SDL: hints: Change hints to be backed by Properties, add documentation. (#9892)

From 074dd8c35f0b3769b033b86b2a16628d0811b01d Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Mon, 5 Aug 2024 12:02:28 -0400
Subject: [PATCH] hints: Change hints to be backed by Properties, add
 documentation. (#9892)

This makes the subsystem thread-safe, more performant, and cleans up the code a little.

Also removed SDL_HINT_WINDOWS_FORCE_MUTEX_CRITICAL_SECTIONS, since setting this hint programmatically initializes properties, which creates a lock, so we can't check hints while creating locks. The slim reader-writer locks have been the default for ages and are solid, so we'll just use those when available.
---
 docs/README-migration.md          |   3 +
 include/SDL3/SDL_hints.h          |  87 ++++----
 src/SDL.c                         |   3 +-
 src/SDL_hints.c                   | 340 ++++++++++++++++--------------
 src/SDL_hints_c.h                 |   3 +-
 src/dynapi/SDL_dynapi_procs.h     |   6 +-
 src/thread/windows/SDL_sysmutex.c |  34 ++-
 7 files changed, 259 insertions(+), 217 deletions(-)

diff --git a/docs/README-migration.md b/docs/README-migration.md
index 5c099f0545f02..916f61f0a0c7e 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -755,6 +755,8 @@ SDL_AddHintCallback() now returns a standard int result instead of void, returni
 
 Calling SDL_GetHint() with the name of the hint being changed from within a hint callback will now return the new value rather than the old value. The old value is still passed as a parameter to the hint callback.
 
+SDL_SetHint, SDL_SetHintWithPriority, and SDL_ResetHint now return int (-1 on error, 0 on success) instead of SDL_bool (SDL_FALSE on error, SDL_TRUE on success).
+
 The environment variables SDL_VIDEODRIVER and SDL_AUDIODRIVER have been renamed to SDL_VIDEO_DRIVER and SDL_AUDIO_DRIVER.
 
 The environment variables SDL_VIDEO_X11_WMCLASS and SDL_VIDEO_WAYLAND_WMCLASS have been removed and replaced by either using the appindentifier param to SDL_SetAppMetadata() or setting SDL_PROP_APP_METADATA_IDENTIFIER_STRING with SDL_SetAppMetadataProperty()
@@ -799,6 +801,7 @@ The following hints have been removed:
 * SDL_HINT_VIDEO_X11_XINERAMA - Xinerama no longer supported by the X11 backend
 * SDL_HINT_VIDEO_X11_XVIDMODE - Xvidmode no longer supported by the X11 backend
 * SDL_HINT_WINDOWS_DISABLE_THREAD_NAMING - SDL now properly handles the 0x406D1388 Exception if no debugger intercepts it, preventing its propagation.
+* SDL_HINT_WINDOWS_FORCE_MUTEX_CRITICAL_SECTIONS - Slim Reader/Writer Locks are always used if available
 * SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4 - replaced with SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, defaulting to SDL_TRUE
 * SDL_HINT_XINPUT_USE_OLD_JOYSTICK_MAPPING
 * SDL_HINT_AUDIO_DEVICE_APP_NAME - replaced by either using the appname param to SDL_SetAppMetadata() or setting SDL_PROP_APP_METADATA_NAME_STRING with SDL_SetAppMetadataProperty()
diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index 3f3d44b55e9ce..3e9de6051973d 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -3837,27 +3837,6 @@ extern "C" {
  */
 #define SDL_HINT_WINDOWS_RAW_KEYBOARD   "SDL_WINDOWS_RAW_KEYBOARD"
 
-/**
- * A variable controlling whether SDL uses Critical Sections for mutexes on
- * Windows.
- *
- * On Windows 7 and newer, Slim Reader/Writer Locks are available. They offer
- * better performance, allocate no kernel resources and use less memory. SDL
- * will fall back to Critical Sections on older OS versions or if forced to by
- * this hint.
- *
- * The variable can be set to the following values:
- *
- * - "0": Use SRW Locks when available, otherwise fall back to Critical
- *   Sections. (default)
- * - "1": Force the use of Critical Sections in all cases.
- *
- * This hint should be set before SDL is initialized.
- *
- * \since This hint is available since SDL 3.0.0.
- */
-#define SDL_HINT_WINDOWS_FORCE_MUTEX_CRITICAL_SECTIONS "SDL_WINDOWS_FORCE_MUTEX_CRITICAL_SECTIONS"
-
 /**
  * A variable controlling whether SDL uses Kernel Semaphores on Windows.
  *
@@ -4133,7 +4112,10 @@ typedef enum SDL_HintPriority
  * \param name the hint to set.
  * \param value the value of the hint variable.
  * \param priority the SDL_HintPriority level for the hint.
- * \returns SDL_TRUE if the hint was set, SDL_FALSE otherwise.
+ * \returns 0 on success or a negative error code 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 3.0.0.
  *
@@ -4141,9 +4123,9 @@ typedef enum SDL_HintPriority
  * \sa SDL_ResetHint
  * \sa SDL_SetHint
  */
-extern SDL_DECLSPEC SDL_bool SDLCALL SDL_SetHintWithPriority(const char *name,
-                                                         const char *value,
-                                                         SDL_HintPriority priority);
+extern SDL_DECLSPEC int SDLCALL SDL_SetHintWithPriority(const char *name,
+                                                        const char *value,
+                                                        SDL_HintPriority priority);
 
 /**
  * Set a hint with normal priority.
@@ -4154,7 +4136,10 @@ extern SDL_DECLSPEC SDL_bool SDLCALL SDL_SetHintWithPriority(const char *name,
  *
  * \param name the hint to set.
  * \param value the value of the hint variable.
- * \returns SDL_TRUE if the hint was set, SDL_FALSE otherwise.
+ * \returns 0 on success or a negative error code 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 3.0.0.
  *
@@ -4162,8 +4147,7 @@ extern SDL_DECLSPEC SDL_bool SDLCALL SDL_SetHintWithPriority(const char *name,
  * \sa SDL_ResetHint
  * \sa SDL_SetHintWithPriority
  */
-extern SDL_DECLSPEC SDL_bool SDLCALL SDL_SetHint(const char *name,
-                                             const char *value);
+extern SDL_DECLSPEC int SDLCALL SDL_SetHint(const char *name, const char *value);
 
 /**
  * Reset a hint to the default value.
@@ -4173,14 +4157,17 @@ extern SDL_DECLSPEC SDL_bool SDLCALL SDL_SetHint(const char *name,
  * change.
  *
  * \param name the hint to set.
- * \returns SDL_TRUE if the hint was set, SDL_FALSE otherwise.
+ * \returns 0 on success or a negative error code 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 3.0.0.
  *
  * \sa SDL_SetHint
  * \sa SDL_ResetHints
  */
-extern SDL_DECLSPEC SDL_bool SDLCALL SDL_ResetHint(const char *name);
+extern SDL_DECLSPEC int SDLCALL SDL_ResetHint(const char *name);
 
 /**
  * Reset all hints to the default values.
@@ -4189,6 +4176,8 @@ extern SDL_DECLSPEC SDL_bool SDLCALL SDL_ResetHint(const char *name);
  * variable, or NULL if the environment isn't set. Callbacks will be called
  * normally with this change.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.0.0.
  *
  * \sa SDL_ResetHint
@@ -4201,6 +4190,13 @@ extern SDL_DECLSPEC void SDLCALL SDL_ResetHints(void);
  * \param name the hint to query.
  * \returns the string value of a hint or NULL if the hint isn't set.
  *
+ * \threadsafety It is safe to call this function from any thread, however
+ *               the return value only remains valid until the hint is
+ *               changed; if another thread might do so, the app should
+ *               supply locks and/or make a copy of the string. Note that
+ *               using a hint callback instead is always thread-safe, as SDL
+ *               holds a lock on the thread subsystem during the callback.
+ *
  * \since This function is available since SDL 3.0.0.
  *
  * \sa SDL_SetHint
@@ -4216,6 +4212,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetHint(const char *name);
  * \returns the boolean value of a hint or the provided default value if the
  *          hint does not exist.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.0.0.
  *
  * \sa SDL_GetHint
@@ -4224,37 +4222,48 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetHint(const char *name);
 extern SDL_DECLSPEC SDL_bool SDLCALL SDL_GetHintBoolean(const char *name, SDL_bool default_value);
 
 /**
- * Type definition of the hint callback function.
+ * A callback used to send notifications of hint value changes.
+ *
+ * This is called an initial time during SDL_AddHintCallback with the hint's
+ * current value, and then again each time the hint's value changes.
  *
  * \param userdata what was passed as `userdata` to SDL_AddHintCallback().
  * \param name what was passed as `name` to SDL_AddHintCallback().
  * \param oldValue the previous hint value.
  * \param newValue the new value hint is to be set to.
  *
+ * \threadsafety This callback is fired from whatever thread is setting a
+ *               new hint value. SDL holds a lock on the hint subsystem when
+ *               calling this callback.
+ *
  * \since This datatype is available since SDL 3.0.0.
+ *
+ * \sa SDL_AddHintCallback
  */
 typedef void (SDLCALL *SDL_HintCallback)(void *userdata, const char *name, const char *oldValue, const char *newValue);
 
 /**
  * Add a function to watch a particular hint.
  *
+ * The callback function is called _during_ this function, to provide it an
+ * initial value, and again each time the hint's value changes.
+ *
  * \param name the hint to watch.
- * \param callback an SDL_HintCallback function that will be called when the
+ * \param callback An SDL_HintCallback function that will be called when the
  *                 hint value changes.
  * \param userdata a pointer to pass to the callback function.
  * \returns 0 on success or a negative error code on failure; call
  *          SDL_GetError() for more information.
  *
- * \threadsafety It is **NOT** safe to call this function from two threads at
- *               once.
+ * \threadsafety It is safe to call this function from any thread.
  *
  * \since This function is available since SDL 3.0.0.
  *
  * \sa SDL_DelHintCallback
  */
 extern SDL_DECLSPEC int SDLCALL SDL_AddHintCallback(const char *name,
-                                                SDL_HintCallback callback,
-                                                void *userdata);
+                                                    SDL_HintCallback callback,
+                                                    void *userdata);
 
 /**
  * Remove a function watching a particular hint.
@@ -4264,13 +4273,15 @@ extern SDL_DECLSPEC int SDLCALL SDL_AddHintCallback(const char *name,
  *                 hint value changes.
  * \param userdata a pointer being passed to the callback function.
  *
+ * \threadsafety It is safe to call this function from any thread.
+ *
  * \since This function is available since SDL 3.0.0.
  *
  * \sa SDL_AddHintCallback
  */
 extern SDL_DECLSPEC void SDLCALL SDL_DelHintCallback(const char *name,
-                                                 SDL_HintCallback callback,
-                                                 void *userdata);
+                                                     SDL_HintCallback callback,
+                                                     void *userdata);
 
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
diff --git a/src/SDL.c b/src/SDL.c
index 798d68fccff9a..d037f3b485db7 100644
--- a/src/SDL.c
+++ b/src/SDL.c
@@ -255,10 +255,12 @@ void SDL_InitMainThread(void)
     SDL_InitLog();
     SDL_InitProperties();
     SDL_GetGlobalProperties();
+    SDL_InitHints();
 }
 
 static void SDL_QuitMainThread(void)
 {
+    SDL_QuitHints();
     SDL_QuitProperties();
     SDL_QuitLog();
     SDL_QuitFilesystem();
@@ -623,7 +625,6 @@ void SDL_Quit(void)
 #endif
 
     SDL_SetObjectsInvalid();
-    SDL_ClearHints();
     SDL_AssertionsQuit();
 
     SDL_QuitPixelFormatDetails();
diff --git a/src/SDL_hints.c b/src/SDL_hints.c
index 1abbeb930c135..48b3e6ac795e6 100644
--- a/src/SDL_hints.c
+++ b/src/SDL_hints.c
@@ -22,9 +22,6 @@
 
 #include "SDL_hints_c.h"
 
-/* Assuming there aren't many hints set and they aren't being queried in
-   critical performance paths, we'll just use linked lists here.
- */
 typedef struct SDL_HintWatch
 {
     SDL_HintCallback callback;
@@ -34,42 +31,71 @@ typedef struct SDL_HintWatch
 
 typedef struct SDL_Hint
 {
-    char *name;
     char *value;
     SDL_HintPriority priority;
     SDL_HintWatch *callbacks;
-    struct SDL_Hint *next;
 } SDL_Hint;
 
-static SDL_Hint *SDL_hints;
+static SDL_PropertiesID SDL_hint_props = 0;
 
-SDL_bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPriority priority)
+static SDL_PropertiesID GetHintProperties(SDL_bool create)
 {
-    const char *env;
-    SDL_Hint *hint;
-    SDL_HintWatch *entry;
+    if (!SDL_hint_props && create) {
+        SDL_hint_props = SDL_CreateProperties();
+    }
+    return SDL_hint_props;
+}
 
-    if (!name) {
-        return SDL_FALSE;
+void SDL_InitHints(void)
+{
+    // Just make sure the hint properties are created on the main thread
+    (void)GetHintProperties(SDL_TRUE);
+}
+
+static void SDLCALL CleanupHintProperty(void *userdata, void *value)
+{
+    SDL_Hint *hint = (SDL_Hint *) value;
+    SDL_free(hint->value);
+
+    SDL_HintWatch *entry = hint->callbacks;
+    while (entry) {
+        SDL_HintWatch *freeable = entry;
+        entry = entry->next;
+        SDL_free(freeable);
     }
+    SDL_free(hint);
+}
 
-    env = SDL_getenv(name);
-    if (env && priority < SDL_HINT_OVERRIDE) {
-        return SDL_FALSE;
+int SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPriority priority)
+{
+    if (!name || !*name) {
+        return SDL_InvalidParamError("name");
     }
 
-    for (hint = SDL_hints; hint; hint = hint->next) {
-        if (SDL_strcmp(name, hint->name) == 0) {
-            if (priority < hint->priority) {
-                return SDL_FALSE;
-            }
-            if (hint->value != value &&
-                (!value || !hint->value || SDL_strcmp(hint->value, value) != 0)) {
+    const char *env = SDL_getenv(name);
+    if (env && (priority < SDL_HINT_OVERRIDE)) {
+        return SDL_SetError("An environment variable is taking priority");
+    }
+
+    const SDL_PropertiesID hints = GetHintProperties(SDL_TRUE);
+    if (!hints) {
+        return -1;
+    }
+
+    int retval = -1;
+
+    SDL_LockProperties(hints);
+
+    SDL_Hint *hint = SDL_GetPointerProperty(hints, name, NULL);
+    if (hint) {
+        if (priority >= hint->priority) {
+            if (hint->value != value && (!value || !hint->value || SDL_strcmp(hint->value, value) != 0)) {
                 char *old_value = hint->value;
 
                 hint->value = value ? SDL_strdup(value) : NULL;
-                for (entry = hint->callbacks; entry;) {
-                    /* Save the next entry in case this one is deleted */
+                SDL_HintWatch *entry = hint->callbacks;
+                while (entry) {
+                    // Save the next entry in case this one is deleted
                     SDL_HintWatch *next = entry->next;
                     entry->callback(entry->userdata, name, old_value, value);
                     entry = next;
@@ -77,104 +103,118 @@ SDL_bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPr
                 SDL_free(old_value);
             }
             hint->priority = priority;
-            return SDL_TRUE;
+            retval = 0;
+        }
+    } else {  // Couldn't find the hint? Add a new one.
+        hint = (SDL_Hint *)SDL_malloc(sizeof(*hint));
+        if (hint) {
+            hint->value = value ? SDL_strdup(value) : NULL;
+            hint->priority = priority;
+            hint->callbacks = NULL;
+            retval = (SDL_SetPointerPropertyWithCleanup(hints, name, hint, CleanupHintProperty, NULL) != -1);
         }
     }
 
-    /* Couldn't find the hint, add a new one */
-    hint = (SDL_Hint *)SDL_malloc(sizeof(*hint));
-    if (!hint) {
-        return SDL_FALSE;
-    }
-    hint->name = SDL_strdup(name);
-    hint->value = value ? SDL_strdup(value) : NULL;
-    hint->priority = priority;
-    hint->callbacks = NULL;
-    hint->next = SDL_hints;
-    SDL_hints = hint;
-    return SDL_TRUE;
+    SDL_UnlockProperties(hints);
+
+    return retval;
 }
 
-SDL_bool SDL_ResetHint(const char *name)
+int SDL_ResetHint(const char *name)
 {
-    const char *env;
-    SDL_Hint *hint;
-    SDL_HintWatch *entry;
-
-    if (!name) {
-        return SDL_FALSE;
+    if (!name || !*name) {
+        return SDL_InvalidParamError("name");
     }
 
-    env = SDL_getenv(name);
-    for (hint = SDL_hints; hint; hint = hint->next) {
-        if (SDL_strcmp(name, hint->name) == 0) {
-            if ((!env && hint->value) ||
-                (env && !hint->value) ||
-                (env && SDL_strcmp(env, hint->value) != 0)) {
-                for (entry = hint->callbacks; entry;) {
-                    /* Save the next entry in case this one is deleted */
-                    SDL_HintWatch *next = entry->next;
-                    entry->callback(entry->userdata, name, hint->value, env);
-                    entry = next;
-                }
-            }
-            SDL_free(hint->value);
-            hint->value = NULL;
-            hint->priority = SDL_HINT_DEFAULT;
-            return SDL_TRUE;
-        }
+    const char *env = SDL_getenv(name);
+
+    const SDL_PropertiesID hints = GetHintProperties(SDL_FALSE);
+    if (!hints) {
+        return -1;
     }
-    return SDL_FALSE;
-}
 
-void SDL_ResetHints(void)
-{
-    const char *env;
-    SDL_Hint *hint;
-    SDL_HintWatch *entry;
-
-    for (hint = SDL_hints; hint; hint = hint->next) {
-        env = SDL_getenv(hint->name);
-        if ((!env && hint->value) ||
-            (env && !hint->value) ||
-            (env && SDL_strcmp(env, hint->value) != 0)) {
-            for (entry = hint->callbacks; entry;) {
-                /* Save the next entry in case this one is deleted */
+    int retval = -1;
+
+    SDL_LockProperties(hints);
+
+    SDL_Hint *hint = SDL_GetPointerProperty(hints, name, NULL);
+    if (hint) {
+        if ((!env && hint->value) || (env && !hint->value) || (env && SDL_strcmp(env, hint->value) != 0)) {
+            for (SDL_HintWatch *entry = hint->callbacks; entry;) {
+                // Save the next entry in case this one is deleted
                 SDL_HintWatch *next = entry->next;
-                entry->callback(entry->userdata, hint->name, hint->value, env);
+                entry->callback(entry->userdata, name, hint->value, env);
                 entry = next;
             }
         }
         SDL_free(hint->value);
         hint->value = NULL;
         hint->priority = SDL_HINT_DEFAULT;
+        retval = 0;
+    }
+
+    SDL_UnlockProperties(hints);
+
+    return retval;
+}
+
+static void SDLCALL ResetHintsCallback(void *userdata, SDL_PropertiesID hints, const char *name)
+{
+    SDL_Hint *hint = SDL_GetPointerProperty(hints, name, NULL);
+    if (!hint) {
+        return;  // uh...okay.
+    }
+
+    const char *env = SDL_getenv(name);
+    if ((!env && hint->value) || (env && !hint->value) || (env && SDL_strcmp(env, hint->value) != 0)) {
+        SDL_HintWatch *entry = hint->callbacks;
+        while (entry) {
+            // Save the next entry in case this one is deleted
+            SDL_HintWatch *next = entry->next;
+            entry->callback(entry->userdata, name, hint->value, env);
+            entry = next;
+        }
     }
+    SDL_free(hint->value);
+    hint->value = NULL;
+    hint->priority = SDL_HINT_DEFAULT;
 }
 
-SDL_bool SDL_SetHint(const char *name, const char *value)
+void SDL_ResetHints(void)
+{
+    SDL_EnumerateProperties(GetHintProperties(SDL_FALSE), ResetHintsCallback, NULL);
+}
+
+int SDL_SetHint(const char *name, const char *value)
 {
     return SDL_SetHintWithPriority(name, value, SDL_HINT_NORMAL);
 }
 
 const char *SDL_GetHint(const char *name)
 {
-    const char *env;
-    SDL_Hint *hint;
-
     if (!name) {
         return NULL;
     }
 
-    env = SDL_getenv(name);
-    for (hint = SDL_hints; hint; hint = hint->next) {
-        if (SDL_strcmp(name, hint->name) == 0) {
-            if (!env || hint->priority == SDL_HINT_OVERRIDE) {
-                return SDL_GetPersistentString(hint->value);
-            }
-            break;
+    const SDL_PropertiesID hints = GetHintProperties(SDL_FALSE);
+    if (!hints) {
+        return NULL;
+    }
+
+    const char *retval = SDL_getenv(name);
+
+    SDL_LockProperties(hints);
+
+    SDL_Hint *hint = SDL_GetPointerProperty(hints, name, NULL);
+    if (hint) {
+        if (!retval || hint->priority == SDL_HINT_OVERRIDE) {
+            retval = SDL_GetPersistentString(hint->value);
         }
     }
-    return env;
+
+    SDL_UnlockProperties(hints);
+
+    return retval;
 }
 
 int SDL_GetStringInteger(const char *value, int default_value)
@@ -213,102 +253,92 @@ SDL_bool SDL_GetHintBoolean(const char *name, SDL_bool default_value)
 
 int SDL_AddHintCallback(const char *name, SDL_HintCallback callback, void *userdata)
 {
-    SDL_Hint *hint;
-    SDL_HintWatch *entry;
-    const char *value;
-
     if (!name || !*name) {
         return SDL_InvalidParamError("name");
-    }
-    if (!callback) {
+    } else if (!callback) {
         return SDL_InvalidParamError("callback");
     }
 
-    SDL_DelHintCallback(name, callback, userdata);
+    const SDL_PropertiesID hints = GetHintProperties(SDL_TRUE);
+    if (!hints) {
+        return -1;
+    }
 
-    entry = (SDL_HintWatch *)SDL_malloc(sizeof(*entry));
+    SDL_HintWatch *entry = (SDL_HintWatch *)SDL_malloc(sizeof(*entry));
     if (!entry) {
         return -1;
     }
     entry->callback = callback;
     entry->userdata = userdata;
 
-    for (hint = SDL_hints; hint; hint = hint->next) {
-        if (SDL_strcmp(name, hint->name) == 0) {
-            break;
-        }
-    }
-    if (!hint) {
-        /* Need to add a hint entry for this watcher */
+    int retval = -1;
+
+    SDL_LockProperties(hints);
+
+    SDL_DelHintCallback(name, callback, userdata);
+
+    SDL_Hint *hint = SDL_GetPointerProperty(hints, name, NULL);
+    if (hint) {
+        retval = 0;
+    } else {  // Need to add a hint entry for this watcher
         hint = (SDL_Hint *)SDL_malloc(sizeof(*hint));
         if (!hint) {
             SDL_free(entry);
-            return -1;
-        }
-        hint->name = SDL_strdup(name);
-        if (!hint->name) {
-            SDL_free(entry);
-            SDL_free(hint);
-            return -1;
+        } else {
+            hint->value = NULL;
+            hint->priority = SDL_HINT_DEFAULT;
+            hint->callbacks = NULL;
+            retval = SDL_SetPointerPropertyWithCleanup(hints, name, hint, CleanupHintProperty, NULL);
         }
-        hint->value = NULL;
-        hint->priority = SDL_HINT_DEFAULT;
-        hint->callbacks = NULL;
-        hint->next = SDL_hints;
-        SDL_hints = hint;
     }
 
-    /* Add it to the callbacks for this hint */
+    // Add it to the callbacks for this hint
     entry->next = hint->callbacks;
     hint->callbacks = entry;
 
-    /* Now call it with the current value */
-    value = SDL_GetHint(name);
+    // Now call it with the current value
+    const char *value = SDL_GetHint(name);
     callback(userdata, name, value, value);
-    return 0;
+
+    SDL_UnlockProperties(hints);
+
+    return retval;
 }
 
 void SDL_DelHintCallback(const char *name, SDL_HintCallback callback, void *userdata)
 {
-    SDL_Hint *hint;
-    SDL_HintWatch *entry, *prev;
-
-    for (hint = SDL_hints; hint; hint = hint->next) {
-        if (SDL_strcmp(name, hint->name) == 0) {
-            prev = NULL;
-            for (entry = hint->callbacks; entry; entry = entry->next) {
-                if (callback == entry->callback && userdata == entry->userdata) {
-                    if (prev) {
-                        prev->next = entry->next;
-                    } else {
-                        hint->callbacks = entry->next;
-                    }
-                    SDL_free(entry);
-                    break;
+    if (!name || !*name) {
+        return;
+    }
+
+    const SDL_PropertiesID hints = GetHintProperties(SDL_FALSE);
+    if (!hints) {
+        return;
+    }
+
+    SDL_LockProperties(hints);
+    SDL_Hint *hint = SDL_GetPointerProperty(hints, name, NULL);
+    if (hint) {
+        SDL_HintWatch *prev = NULL;
+        for (SDL_HintWatch *entry = hint->callbacks; entry; entry = entry->next) {
+            if ((callback == entry->callback) && (userdata == entry->userdata)) {
+                if (prev) {
+                    prev->next = entry->next;
+                } else {
+                    hint->callbacks = entry->next;
                 }
-                prev = entry;
+                SDL_free(entry);
+                break;
             }
-            return;
+            prev = entry;
         }
     }
+    SDL_UnlockProperties(hints);
 }
 
-void SDL_ClearHints(void)
+void SDL_QuitHints(void)
 {
-    SDL_Hint *hint;
-    SDL_HintWatch *entry;
-
-    while (SDL_hints) {
-        hint = SDL_hints;
-        SDL_hints = hint->next;
-
-        SDL_free(hint->name);
-        SDL_free(hint->value);
-        for (entry = hint->callbacks; entry;) {
-            SDL_HintWatch *freeable = entry;
-            entry = entry->next;
-            SDL_free(freeable);
-        }
-        SDL_free(hint);
-    }
+    SDL_DestroyProperties(SDL_hint_props);
+    SDL_hint_props = 0;
 }
+
diff --git a/src/SDL_hints_c.h b/src/SDL_hints_c.h
index 486280ec6b2d8..bd33ad8ac8b90 100644
--- a/src/SDL_hints_c.h
+++ b/src/SDL_hints_c.h
@@ -25,8 +25,9 @@
 #ifndef SDL_hints_c_h_
 #define SDL_hints_c_h_
 
+extern void SDL_InitHints(void);
 extern SDL_bool SDL_GetStringBoolean(const char *value, SDL_bool default_value);
 extern int SDL_GetStringInteger(const char *value, int default_value);
-extern void SDL_ClearHints(void);
+extern void SDL_QuitHints(void);
 
 #endif /* SDL_hints_c_h_ */
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 7c232e31707cd..faf99fa0f58e1 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -711,7 +711,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_RenderViewportSet,(SDL_Renderer *a),(a),return)
 SDL_DYNAPI_PROC(SDL_AssertState,SDL_ReportAssertion,(SDL_AssertData *a, const char *b, const char *c, int d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(int,SDL_RequestAndroidPermission,(const char *a, SDL_RequestAndroidPermissionCallback b, void *c),(a,b,c),return)
 SDL_DYNAPI_PROC(void,SDL_ResetAssertionReport,(void),(),)
-SDL_DYNAPI_PROC(SDL_bool,SDL_ResetHint,(const char *a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_ResetHint,(const char *a),(a),return)
 SDL_DYNAPI_PROC(void,SDL_ResetHints,(void),(),)
 SDL_DYNAPI_PROC(void,SDL_ResetKeyboard,(void),(),)
 SDL_DYNAPI_PROC(void,SDL_ResetLogPriorities,(void),(),)
@@ -762,8 +762,8 @@ SDL_DYNAPI_PROC(int,SDL_SetGamepadPlayerIndex,(SDL_Gamepad *a, int b),(a,b),retu
 SDL_DYNAPI_PROC(int,SDL_SetGamepadSensorEnabled,(SDL_Gamepad *a, SDL_SensorType b, SDL_bool c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_SetHapticAutocenter,(SDL_Haptic *a, int b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetHapticGain,(SDL_Haptic *a, int b),(a,b),return)
-SDL_DYNAPI_PROC(SDL_bool,SDL_SetHint,(const char *a, const char *b),(a,b),return)
-SDL_DYNAPI_PROC(SDL_bool,SDL_SetHintWithPriority,(const char *a, const char *b, SDL_HintPriority c),(a,b,c),return)
+SDL_DYNAPI_PROC(int,SDL_SetHint,(const char *a, const char *b),(a,b),return)
+SDL_DYNAPI_PROC(int,SDL_SetHintWithPriority,(const char *a, const char *b, SDL_HintPriority c),(a,b,c),return)
 SDL_DYNAPI_PROC(void,SDL_SetJoystickEventsEnabled,(SDL_bool a),(a),)
 SDL_DYNAPI_PROC(int,SDL_SetJoystickLED,(SDL_Joystick *a, Uint8 b, Uint8 c, Uint8 d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(int,SDL_SetJoystickPlayerIndex,(SDL_Joystick *a, int b),(a,b),return)
diff --git a/src/thread/windows/SDL_sysmutex.c b/src/thread/windows/SDL_sysmutex.c
index 6ad755d7fa2de..115d38f45c0de 100644
--- a/src/thread/windows/SDL_sysmutex.c
+++ b/src/thread/windows/SDL_sysmutex.c
@@ -194,29 +194,25 @@ static const SDL_mutex_impl_t SDL_mutex_impl_cs = {
 SDL_Mutex *SDL_CreateMutex(void)
 {
     if (!SDL_mutex_impl_active.Create) {
-        // Default to fallback implementation
-        const SDL_mutex_impl_t *impl = &SDL_mutex_impl_cs;
-
-        if (!SDL_GetHintBoolean(SDL_HINT_WINDOWS_FORCE_MUTEX_CRITICAL_SECTIONS, SDL_FALSE)) {
 #ifdef SDL_PLATFORM_WINRT
-            // Link statically on this platform
-            impl = &SDL_mutex_impl_srw;
+        const SDL_mutex_impl_t *impl = &SDL_mutex_impl_srw;
 #else
-            // Try faster implementation for Windows 7 and newer
-            HMODULE kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
-            if (kernel32) {
-                // Requires Vista:
-                pInitializeSRWLock = (pfnInitializeSRWLock)GetProcAddress(kernel32, "InitializeSRWLock");
-                pReleaseSRWLockExclusive = (pfnReleaseSRWLockExclusive)GetProcAddress(kernel32, "ReleaseSRWLockExclusive");
-                pAcquireSRWLockExclusive = (pfnAcquireSRWLockExclusive)GetProcAddress(kernel32, "AcquireSRWLockExclusive");
-                // Requires 7:
-                pTryAcquireSRWLockExclusive = (pfnTryAcquireSRWLockExclusive)GetProcAddress(kernel32, "TryAcquireSRWLockExclusive");
-                if (pInitializeSRWLock && pReleaseSRWLockExclusive && pAcquireSRWLockExclusive && pTryAcquireSRWLockExclusive) {
-                    impl = &SDL_mutex_impl_srw;
-                }
+        const SDL_mutex_impl_t *impl = &SDL_mutex_impl_cs;
+
+        // Try faster implementation for Windows 7 and newer
+        HMODULE kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
+        if (kernel32) {
+            // Requires Vista:
+            pInitializeSRWLock = (pfnInitializeSRWLock)GetProcAddress(kernel32, "InitializeSRWLock");
+            pReleaseSRWLockExclusive = (pfnReleaseSRWLockExclusive)GetProcAddress(kernel32, "ReleaseSRWLockExclusive");
+            pAcquireSRWLockExclusive = (pfnAcquireSRWLockExclusive)GetProcAddress(kernel32, "AcquireSRWLockExclusive");
+            // Requires 7:
+            pTryAcquireSRWLockExclusive = (pfnTryAcquireSRWLockExclusive)GetProcAddress(kernel32, "TryAcquireSRWLockExclusive");
+            if (pInitializeSRWLock && pReleaseSRWLockExclusive && pAcquireSRWLockExclusive && pTryAcquireSRWLockExclusive) {
+                impl = &SDL_mutex_impl_srw;
             }
-#endif
         }
+#endif // SDL_PLATFORM_WINRT
 
         // Copy instead of using pointer to save one level of indirection
         SDL_copyp(&SDL_mutex_impl_active, impl);