SDL_ttf: Made SDL_Init(), SDL_Quit(), and TTF_OpenFont*() thread-safe.

From df64d50c8406298de0430c20c88d78199f3811cb Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 26 Sep 2024 18:33:00 -0700
Subject: [PATCH] Made SDL_Init(), SDL_Quit(), and TTF_OpenFont*() thread-safe.

Also documented the thread-safety of other API functions.

Fixes https://github.com/libsdl-org/SDL_ttf/issues/28
---
 external/SDL               |   2 +-
 include/SDL3_ttf/SDL_ttf.h | 108 +++++++++++++++++++++++++-
 src/SDL_ttf.c              | 150 +++++++++++++++++++++++++++----------
 3 files changed, 220 insertions(+), 40 deletions(-)

diff --git a/external/SDL b/external/SDL
index 53bf2baa..d8c76d2f 160000
--- a/external/SDL
+++ b/external/SDL
@@ -1 +1 @@
-Subproject commit 53bf2baac3ca32f49c62963a15e6140c696a33cf
+Subproject commit d8c76d2f348b1521e55ad73bb155470d63ee1b75
diff --git a/include/SDL3_ttf/SDL_ttf.h b/include/SDL3_ttf/SDL_ttf.h
index bba1cc09..c983240e 100644
--- a/include/SDL3_ttf/SDL_ttf.h
+++ b/include/SDL3_ttf/SDL_ttf.h
@@ -70,6 +70,8 @@ extern "C" {
  *
  * \returns SDL_ttf version.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC int SDLCALL TTF_Version(void);
@@ -83,6 +85,8 @@ extern SDL_DECLSPEC int SDLCALL TTF_Version(void);
  * \param minor to be filled in with the minor version number. Can be NULL.
  * \param patch to be filled in with the param version number. Can be NULL.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_Init
@@ -98,6 +102,8 @@ extern SDL_DECLSPEC void SDLCALL TTF_GetFreeTypeVersion(int *major, int *minor,
  * \param minor to be filled in with the minor version number. Can be NULL.
  * \param patch to be filled in with the param version number. Can be NULL.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC void SDLCALL TTF_GetHarfBuzzVersion(int *major, int *minor, int *patch);
@@ -146,6 +152,8 @@ extern SDL_DECLSPEC bool SDLCALL TTF_Init(void);
  * \returns a valid TTF_Font, or NULL on failure; call SDL_GetError() for more
  *          information.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_CloseFont
@@ -171,6 +179,8 @@ extern SDL_DECLSPEC TTF_Font * SDLCALL TTF_OpenFont(const char *file, int ptsize
  * \returns a valid TTF_Font, or NULL on failure; call SDL_GetError() for more
  *          information.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_CloseFont
@@ -207,6 +217,8 @@ extern SDL_DECLSPEC TTF_Font * SDLCALL TTF_OpenFontIO(SDL_IOStream *src, bool cl
  * \returns a valid TTF_Font, or NULL on failure; call SDL_GetError() for more
  *          information.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_CloseFont
@@ -231,6 +243,8 @@ extern SDL_DECLSPEC TTF_Font * SDLCALL TTF_OpenFontWithProperties(SDL_Properties
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC bool SDLCALL TTF_SetFontSize(TTF_Font *font, int ptsize);
@@ -247,6 +261,8 @@ extern SDL_DECLSPEC bool SDLCALL TTF_SetFontSize(TTF_Font *font, int ptsize);
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC bool SDLCALL TTF_SetFontSizeDPI(TTF_Font *font, int ptsize, unsigned int hdpi, unsigned int vdpi);
@@ -274,6 +290,8 @@ extern SDL_DECLSPEC bool SDLCALL TTF_SetFontSizeDPI(TTF_Font *font, int ptsize,
  * \param font the font to query.
  * \returns the current font style, as a set of bit flags.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_SetFontStyle
@@ -296,6 +314,8 @@ extern SDL_DECLSPEC int SDLCALL TTF_GetFontStyle(const TTF_Font *font);
  * \param font the font to set a new style on.
  * \param style the new style values to set, OR'd together.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_GetFontStyle
@@ -308,6 +328,8 @@ extern SDL_DECLSPEC void SDLCALL TTF_SetFontStyle(TTF_Font *font, int style);
  * \param font the font to query.
  * \returns the font's current outline value.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_SetFontOutline
@@ -319,12 +341,16 @@ extern SDL_DECLSPEC int SDLCALL TTF_GetFontOutline(const TTF_Font *font);
  *
  * \param font the font to set a new outline on.
  * \param outline positive outline value, 0 to default.
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ *          information.
+ *
+ * \threadsafety This function should be called on the thread that created the font.
  *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_GetFontOutline
  */
-extern SDL_DECLSPEC void SDLCALL TTF_SetFontOutline(TTF_Font *font, int outline);
+extern SDL_DECLSPEC bool SDLCALL TTF_SetFontOutline(TTF_Font *font, int outline);
 
 
 /**
@@ -350,6 +376,8 @@ extern SDL_DECLSPEC void SDLCALL TTF_SetFontOutline(TTF_Font *font, int outline)
  * \param font the font to query.
  * \returns the font's current hinter value.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_SetFontHinting
@@ -372,6 +400,8 @@ extern SDL_DECLSPEC int SDLCALL TTF_GetFontHinting(const TTF_Font *font);
  * \param font the font to set a new hinter setting on.
  * \param hinting the new hinter setting.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_GetFontHinting
@@ -397,6 +427,8 @@ extern SDL_DECLSPEC void SDLCALL TTF_SetFontHinting(TTF_Font *font, int hinting)
  * \param font the font to query.
  * \returns the font's current wrap alignment option.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_SetFontWrappedAlign
@@ -415,6 +447,8 @@ extern SDL_DECLSPEC int SDLCALL TTF_GetFontWrappedAlign(const TTF_Font *font);
  * \param font the font to set a new wrap alignment option on.
  * \param align the new wrap alignment option.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_GetFontWrappedAlign
@@ -429,6 +463,8 @@ extern SDL_DECLSPEC void SDLCALL TTF_SetFontWrappedAlign(TTF_Font *font, int ali
  * \param font the font to query.
  * \returns the font's height.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC int SDLCALL TTF_FontHeight(const TTF_Font *font);
@@ -441,6 +477,8 @@ extern SDL_DECLSPEC int SDLCALL TTF_FontHeight(const TTF_Font *font);
  * \param font the font to query.
  * \returns the font's ascent.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC int SDLCALL TTF_FontAscent(const TTF_Font *font);
@@ -453,6 +491,8 @@ extern SDL_DECLSPEC int SDLCALL TTF_FontAscent(const TTF_Font *font);
  * \param font the font to query.
  * \returns the font's descent.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC int SDLCALL TTF_FontDescent(const TTF_Font *font);
@@ -463,6 +503,8 @@ extern SDL_DECLSPEC int SDLCALL TTF_FontDescent(const TTF_Font *font);
  * \param font the font to query.
  * \returns the font's recommended spacing.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC int SDLCALL TTF_FontLineSkip(const TTF_Font *font);
@@ -473,6 +515,8 @@ extern SDL_DECLSPEC int SDLCALL TTF_FontLineSkip(const TTF_Font *font);
  * \param font the font to query.
  * \returns true if kerning is enabled, false otherwise.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC bool SDLCALL TTF_GetFontKerning(const TTF_Font *font);
@@ -488,6 +532,8 @@ extern SDL_DECLSPEC bool SDLCALL TTF_GetFontKerning(const TTF_Font *font);
  * \param font the font to set kerning on.
  * \param enabled true to enable kerning, false to disable.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC void SDLCALL TTF_SetFontKerning(TTF_Font *font, bool enabled);
@@ -498,6 +544,8 @@ extern SDL_DECLSPEC void SDLCALL TTF_SetFontKerning(TTF_Font *font, bool enabled
  * \param font the font to query.
  * \returns the number of FreeType font faces.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC long SDLCALL TTF_FontFaces(const TTF_Font *font);
@@ -514,6 +562,8 @@ extern SDL_DECLSPEC long SDLCALL TTF_FontFaces(const TTF_Font *font);
  * \param font the font to query.
  * \returns true if the font is fixed-width, false otherwise.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC bool SDLCALL TTF_FontFaceIsFixedWidth(const TTF_Font *font);
@@ -530,6 +580,8 @@ extern SDL_DECLSPEC bool SDLCALL TTF_FontFaceIsFixedWidth(const TTF_Font *font);
  * \param font the font to query.
  * \returns the font's family name.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC const char * SDLCALL TTF_FontFaceFamilyName(const TTF_Font *font);
@@ -546,6 +598,8 @@ extern SDL_DECLSPEC const char * SDLCALL TTF_FontFaceFamilyName(const TTF_Font *
  * \param font the font to query.
  * \returns the font's style name.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC const char * SDLCALL TTF_FontFaceStyleName(const TTF_Font *font);
@@ -557,6 +611,8 @@ extern SDL_DECLSPEC const char * SDLCALL TTF_FontFaceStyleName(const TTF_Font *f
  * \param ch the character code to check.
  * \returns true if font provides a glyph for this character, false if not.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC bool SDLCALL TTF_GlyphIsProvided(TTF_Font *font, Uint32 ch);
@@ -585,6 +641,8 @@ extern SDL_DECLSPEC bool SDLCALL TTF_GlyphIsProvided(TTF_Font *font, Uint32 ch);
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC bool SDLCALL TTF_GlyphMetrics(TTF_Font *font, Uint32 ch, int *minx, int *maxx, int *miny, int *maxy, int *advance);
@@ -606,6 +664,8 @@ extern SDL_DECLSPEC bool SDLCALL TTF_GlyphMetrics(TTF_Font *font, Uint32 ch, int
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC bool SDLCALL TTF_SizeText(TTF_Font *font, const char *text, size_t length, int *w, int *h);
@@ -629,6 +689,8 @@ extern SDL_DECLSPEC bool SDLCALL TTF_SizeText(TTF_Font *font, const char *text,
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC bool SDLCALL TTF_MeasureText(TTF_Font *font, const char *text, size_t length, int measure_width, int *extent, int *count);
@@ -657,6 +719,8 @@ extern SDL_DECLSPEC bool SDLCALL TTF_MeasureText(TTF_Font *font, const char *tex
  * \param fg the foreground color for the text.
  * \returns a new 8-bit, palettized surface, or NULL if there was an error.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_RenderText_Blended
@@ -691,6 +755,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_Solid(TTF_Font *font, c
  *                   newline characters.
  * \returns a new 8-bit, palettized surface, or NULL if there was an error.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_RenderText_Blended_Wrapped
@@ -718,6 +784,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_Solid_Wrapped(TTF_Font
  * \param fg the foreground color for the text.
  * \returns a new 8-bit, palettized surface, or NULL if there was an error.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_RenderGlyph_Blended
@@ -752,6 +820,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderGlyph_Solid(TTF_Font *font,
  * \param bg the background color for the text.
  * \returns a new 8-bit, palettized surface, or NULL if there was an error.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_RenderText_Blended
@@ -787,6 +857,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_Shaded(TTF_Font *font,
  *                   newline characters.
  * \returns a new 8-bit, palettized surface, or NULL if there was an error.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_RenderText_Blended_Wrapped
@@ -816,6 +888,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_Shaded_Wrapped(TTF_Font
  * \param bg the background color for the text.
  * \returns a new 8-bit, palettized surface, or NULL if there was an error.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_RenderGlyph_Blended
@@ -848,6 +922,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderGlyph_Shaded(TTF_Font *font,
  * \param fg the foreground color for the text.
  * \returns a new 32-bit, ARGB surface, or NULL if there was an error.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_RenderText_Blended_Wrapped
@@ -881,6 +957,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_Blended(TTF_Font *font,
  *                   newline characters.
  * \returns a new 32-bit, ARGB surface, or NULL if there was an error.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_RenderText_Blended
@@ -908,6 +986,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_Blended_Wrapped(TTF_Fon
  * \param fg the foreground color for the text.
  * \returns a new 32-bit, ARGB surface, or NULL if there was an error.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_RenderGlyph_LCD
@@ -941,6 +1021,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderGlyph_Blended(TTF_Font *font
  * \param bg the background color for the text.
  * \returns a new 32-bit, ARGB surface, or NULL if there was an error.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_RenderText_Blended
@@ -976,6 +1058,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_LCD(TTF_Font *font, con
  *                   newline characters.
  * \returns a new 32-bit, ARGB surface, or NULL if there was an error.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_RenderText_Blended_Wrapped
@@ -1004,6 +1088,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_LCD_Wrapped(TTF_Font *f
  * \param bg the background color for the text.
  * \returns a new 32-bit, ARGB surface, or NULL if there was an error.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_RenderGlyph_Blended
@@ -1027,6 +1113,8 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderGlyph_LCD(TTF_Font *font, Ui
  *
  * \param font the font to dispose of.
  *
+ * \threadsafety This function should not be called while any other thread is using the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_OpenFont
@@ -1057,6 +1145,8 @@ extern SDL_DECLSPEC void SDLCALL TTF_CloseFont(TTF_Font *font);
  * deal with it. A well-written program should call TTF_CloseFont() on any
  * open fonts before calling this function!
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC void SDLCALL TTF_Quit(void);
@@ -1076,6 +1166,8 @@ extern SDL_DECLSPEC void SDLCALL TTF_Quit(void);
  * \returns the current number of initialization calls, that need to
  *          eventually be paired with this many calls to TTF_Quit().
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_Init
@@ -1091,6 +1183,8 @@ extern SDL_DECLSPEC int SDLCALL TTF_WasInit(void);
  * \param ch the current character's code, 32 bits.
  * \returns The kerning size between the two specified characters, in pixels, or -1 on failure; call SDL_GetError() for more information.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC int TTF_GetFontKerningSizeGlyphs(TTF_Font *font, Uint32 previous_ch, Uint32 ch);
@@ -1108,6 +1202,8 @@ extern SDL_DECLSPEC int TTF_GetFontKerningSizeGlyphs(TTF_Font *font, Uint32 prev
  * \returns true on success or false on failure; call SDL_GetError()
  *          for more information.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_GetFontSDF
@@ -1121,6 +1217,8 @@ extern SDL_DECLSPEC bool TTF_SetFontSDF(TTF_Font *font, bool enabled);
  *
  * \returns true if enabled, false otherwise.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_SetFontSDF
@@ -1157,6 +1255,8 @@ typedef enum TTF_Direction
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC bool SDLCALL TTF_SetFontDirection(TTF_Font *font, TTF_Direction direction);
@@ -1174,6 +1274,8 @@ extern SDL_DECLSPEC bool SDLCALL TTF_SetFontDirection(TTF_Font *font, TTF_Direct
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC bool SDLCALL TTF_SetFontScriptName(TTF_Font *font, const char *script);
@@ -1188,6 +1290,8 @@ extern SDL_DECLSPEC bool SDLCALL TTF_SetFontScriptName(TTF_Font *font, const cha
  * \returns true on success or false on failure; call SDL_GetError()
  *          for more information.
  *
+ * \threadsafety This function should be called on the thread that created the font.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  */
 extern SDL_DECLSPEC bool TTF_SetFontLanguage(TTF_Font *font, const char *language_bcp47);
@@ -1201,6 +1305,8 @@ extern SDL_DECLSPEC bool TTF_SetFontLanguage(TTF_Font *font, const char *languag
  *
  * \returns true if the font is scalable, false otherwise.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL_ttf 3.0.0.
  *
  * \sa TTF_SetFontSDF
diff --git a/src/SDL_ttf.c b/src/SDL_ttf.c
index dda31b4e..5606dab7 100644
--- a/src/SDL_ttf.c
+++ b/src/SDL_ttf.c
@@ -232,6 +232,7 @@ struct TTF_Font {
     /* The font style */
     int style;
     int outline_val;
+    FT_Stroker stroker;
 
     /* Whether kerning is desired */
     bool enable_kerning;
@@ -289,11 +290,17 @@ struct TTF_Font {
 #define TTF_STYLE_NO_GLYPH_CHANGE   (TTF_STYLE_UNDERLINE | TTF_STYLE_STRIKETHROUGH)
 
 /* The FreeType font engine/library */
-static FT_Library library = NULL;
-static int TTF_initialized = 0;
+static struct
+{
+    SDL_InitState init;
+    SDL_AtomicInt refcount;
+    SDL_Mutex *lock;
+    FT_Library library;
+} TTF_state;
 
 #define TTF_CHECK_INITIALIZED(errval)                   \
-    if (!TTF_initialized) {                             \
+    if (SDL_ShouldInit(&TTF_state.init)) {              \
+        SDL_SetInitialized(&TTF_state.init, false);     \
         SDL_SetError("Library not initialized");        \
         return errval;                                  \
     }
@@ -1660,32 +1667,61 @@ bool TTF_Init(void)
     SDL_Log("Sizeof TTF_Image: %d c_glyph: %d TTF_Font: %d", sizeof (TTF_Image), sizeof (c_glyph), sizeof (TTF_Font));
 #endif
 
-    if (!TTF_initialized) {
-        FT_Error error = FT_Init_FreeType(&library);
-        if (error) {
-            result = TTF_SetFTError("Couldn't init FreeType engine", error);
-        }
+    SDL_AtomicIncRef(&TTF_state.refcount);
+
+    if (!SDL_ShouldInit(&TTF_state.init)) {
+        return true;
     }
+
+    FT_Error error = FT_Init_FreeType(&TTF_state.library);
+    if (error) {
+        TTF_SetFTError("Couldn't init FreeType engine", error);
+        result = false;
+    }
+
     if (result) {
-        ++TTF_initialized;
 #if TTF_USE_SDF
-#  if 0
+#if 0
         /* Set various properties of the renderers. */
         int spread = 4;
         int overlaps = 0;
-        FT_Property_Set( library, "bsdf", "spread", &spread);
-        FT_Property_Set( library, "sdf", "spread", &spread);
-        FT_Property_Set( library, "sdf", "overlaps", &overlaps);
-#  endif
+        FT_Property_Set(TTF_state.library, "bsdf", "spread", &spread);
+        FT_Property_Set(TTF_state.library, "sdf", "spread", &spread);
+        FT_Property_Set(TTF_state.library, "sdf", "overlaps", &overlaps);
+#endif
 #endif
+        TTF_state.lock = SDL_CreateMutex();
+    } else {
+        (void)SDL_AtomicDecRef(&TTF_state.refcount);
     }
+    SDL_SetInitialized(&TTF_state.init, result);
+
     return result;
 }
 
-SDL_COMPILE_TIME_ASSERT(FT_Int, sizeof(int) == sizeof(FT_Int)); /* just in case. */
 void TTF_GetFreeTypeVersion(int *major, int *minor, int *patch)
 {
-    FT_Library_Version(library, major, minor, patch);
+    FT_Int ft_major = 0;
+    FT_Int ft_minor = 0;
+    FT_Int ft_patch = 0;
+
+    if (SDL_ShouldInit(&TTF_state.init)) {
+        SDL_SetInitialized(&TTF_state.init, false);
+    } else {
+        SDL_LockMutex(TTF_state.lock);
+        FT_Library_Version(TTF_state.library, &ft_major, &ft_minor, &ft_patch);
+        SDL_UnlockMutex(TTF_state.lock);
+    }
+
+    if (major) {
+        *major = (int)ft_major;
+    }
+    if (minor) {
+        *minor = (int)ft_minor;
+    }
+    if (patch) {
+        *patch = (int)ft_patch;
+    }
 }
 
 void TTF_GetHarfBuzzVersion(int *major, int *minor, int *patch)
@@ -1719,7 +1755,7 @@ static unsigned long IOread(
 
     src = (SDL_IOStream *)stream->descriptor.pointer;
     SDL_SeekIO(src, offset, SDL_IO_SEEK_SET);
-    return SDL_ReadIO(src, buffer, count);
+    return (unsigned long)SDL_ReadIO(src, buffer, count);
 }
 
 TTF_Font *TTF_OpenFontWithProperties(SDL_PropertiesID props)
@@ -1739,7 +1775,8 @@ TTF_Font *TTF_OpenFontWithProperties(SDL_PropertiesID props)
     Sint64 position;
     int i;
 
-    if (!TTF_initialized) {
+    if (SDL_ShouldInit(&TTF_state.init)) {
+        SDL_SetInitialized(&TTF_state.init, false);
         SDL_SetError("Library not initialized");
         if (src && closeio) {
             SDL_CloseIO(src);
@@ -1799,7 +1836,9 @@ TTF_Font *TTF_OpenFontWithProperties(SDL_PropertiesID props)
     font->args.flags = FT_OPEN_STREAM;
     font->args.stream = stream;
 
-    error = FT_Open_Face(library, &font->args, index, &font->face);
+    SDL_LockMutex(TTF_state.lock);
+    error = FT_Open_Face(TTF_state.library, &font->args, index, &font->face);
+    SDL_UnlockMutex(TTF_state.lock);
     if (error || font->face == NULL) {
         TTF_SetFTError("Couldn't load font file", error);
         TTF_CloseFont(font);
@@ -2203,8 +2242,8 @@ static FT_Error Load_Glyph(TTF_Font *font, c_glyph *cached, int want, int transl
         }
 
         /* Render as outline */
-        if ((font->outline_val > 0 && slot->format == FT_GLYPH_FORMAT_OUTLINE)
-            || slot->format == FT_GLYPH_FORMAT_BITMAP) {
+        if ((font->outline_val > 0 && slot->format == FT_GLYPH_FORMAT_OUTLINE) ||
+            slot->format == FT_GLYPH_FORMAT_BITMAP) {
 
             FT_BitmapGlyph bitmap_glyph;
 
@@ -2214,14 +2253,7 @@ static FT_Error Load_Glyph(TTF_Font *font, c_glyph *cached, int want, int transl
             }
 
             if (font->outline_val > 0) {
-                FT_Stroker stroker;
-                error = FT_Stroker_New(library, &stroker);
-                if (error) {
-                    goto ft_failure;
-                }
-                FT_Stroker_Set(stroker, font->outline_val * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
-                FT_Glyph_Stroke(&glyph, stroker, 1 /* delete the original glyph */);
-                FT_Stroker_Done(stroker);
+                FT_Glyph_Stroke(&glyph, font->stroker, 1 /* delete the original glyph */);
             }
 
             /* Render the glyph */
@@ -2714,6 +2746,9 @@ void TTF_CloseFont(TTF_Font *font)
         if (font->face) {
             FT_Done_Face(font->face);
         }
+        if (font->stroker) {
+            FT_Stroker_Done(font->stroker);
+        }
         if (font->args.stream) {
             SDL_free(font->args.stream);
         }
@@ -3668,13 +3703,38 @@ int TTF_GetFontStyle(const TTF_Font *font)
     return style;
 }
 
-void TTF_SetFontOutline(TTF_Font *font, int outline)
+bool TTF_SetFontOutline(TTF_Font *font, int outline)
 {
-    TTF_CHECK_FONT(font,);
+    TTF_CHECK_FONT(font, false);
+
+    outline = SDL_max(0, outline);
+
+    if (outline > 0) {
+        if (!font->stroker) {
+            FT_Error error;
+
+            SDL_LockMutex(TTF_state.lock);
+            error = FT_Stroker_New(TTF_state.library, &font->stroker);
+            SDL_UnlockMutex(TTF_state.lock);
+            if (error) {
+                return TTF_SetFTError("Couldn't create font stroker", error);
+            }
+        }
+
+        FT_Stroker_Set(font->stroker, outline * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
+    } else {
+        if (font->stroker) {
+            FT_Stroker_Done(font->stroker);
+            font->stroker = NULL;
+        }
+    }
+
+    font->outline_val = outline;
 
-    font->outline_val = SDL_max(0, outline);
     TTF_initFontMetrics(font);
     Flush_Cache(font);
+
+    return true;
 }
 
 int TTF_GetFontOutline(const TTF_Font *font)
@@ -3768,17 +3828,31 @@ int TTF_GetFontWrappedAlign(const TTF_Font *font)
 
 void TTF_Quit(void)
 {
-    if (TTF_initialized) {
-        if (--TTF_initialized == 0) {
-            FT_Done_FreeType(library);
-            library = NULL;
-        }
+    if (!SDL_ShouldQuit(&TTF_state.init)) {
+        return;
+    }
+
+    if (!SDL_AtomicDecRef(&TTF_state.refcount)) {
+        SDL_SetInitialized(&TTF_state.init, true);
+        return;
+    }
+
+    if (TTF_state.library) {
+        FT_Done_FreeType(TTF_state.library);
+        TTF_state.library = NULL;
     }
+
+    if (TTF_state.lock) {
+        SDL_DestroyMutex(TTF_state.lock);
+        TTF_state.lock = NULL;
+    }
+
+    SDL_SetInitialized(&TTF_state.init, false);
 }
 
 int TTF_WasInit(void)
 {
-    return TTF_initialized;
+    return SDL_GetAtomicInt(&TTF_state.refcount);
 }
 
 int TTF_GetFontKerningSizeGlyphs(TTF_Font *font, Uint32 previous_ch, Uint32 ch)