SDL: hints: Added SDL_HINT_LOG_BACKENDS.

From 10004ab0eab0defe2d65802e4d861ca2a6c2ba0b Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Mon, 14 Jul 2025 00:14:15 -0400
Subject: [PATCH] hints: Added SDL_HINT_LOG_BACKENDS.

Fixes #13354.
---
 include/SDL3/SDL_hints.h                    | 21 +++++++++++++++++++++
 src/SDL_utils.c                             |  9 +++++++++
 src/SDL_utils_c.h                           |  3 +++
 src/audio/SDL_audio.c                       |  4 +++-
 src/camera/SDL_camera.c                     |  4 +++-
 src/gpu/SDL_gpu.c                           |  1 +
 src/io/io_uring/SDL_asyncio_liburing.c      |  2 ++
 src/io/windows/SDL_asyncio_windows_ioring.c |  2 ++
 src/power/SDL_power.c                       |  2 +-
 src/render/SDL_render.c                     |  4 +++-
 src/storage/SDL_storage.c                   |  8 ++++++--
 src/video/SDL_video.c                       |  4 +++-
 12 files changed, 57 insertions(+), 7 deletions(-)

diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index 30a04c4da416d..51505fe6516e3 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -4381,6 +4381,27 @@ extern "C" {
  */
 #define SDL_HINT_PEN_TOUCH_EVENTS "SDL_PEN_TOUCH_EVENTS"
 
+/**
+ * A variable controlling whether SDL backend information is logged.
+ *
+ * The variable can be set to the following values:
+ *
+ * - "0": Subsystem information will not be logged. (default)
+ * - "1": Subsystem information will be logged.
+ *
+ * This is generally meant to be used as an environment variable to let
+ * end-users report what subsystems were chosen on their system, to aid
+ * in debugging. Logged information is sent through SDL_Log(), which
+ * means by default they appear on stdout on most platforms or maybe
+ * OutputDebugString() on Windows, and can be funneled by the app with
+ * SDL_SetLogOutputFunction(), etc.
+ *
+ * This hint can be set anytime, but the specific logs are generated
+ * during subsystem init.
+ *
+ * \since This hint is available since SDL 3.4.0.
+ */
+#define SDL_HINT_LOG_BACKENDS "SDL_LOG_BACKENDS"
 
 /**
  * An enumeration of hint priorities.
diff --git a/src/SDL_utils.c b/src/SDL_utils.c
index f2090747a8d35..ec2c435a92069 100644
--- a/src/SDL_utils.c
+++ b/src/SDL_utils.c
@@ -552,3 +552,12 @@ char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_nam
 
     return name;
 }
+
+void SDL_LogBackend(const char *subsystem, const char *backend)
+{
+    if (SDL_GetHintBoolean(SDL_HINT_LOG_BACKENDS, false)) {
+        SDL_Log("SDL_BACKEND: %s -> '%s'", subsystem, backend);
+    }
+}
+
+
diff --git a/src/SDL_utils_c.h b/src/SDL_utils_c.h
index 5e0e8f519ebef..2929e7f1f1902 100644
--- a/src/SDL_utils_c.h
+++ b/src/SDL_utils_c.h
@@ -75,4 +75,7 @@ extern const char *SDL_GetPersistentString(const char *string);
 
 extern char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name, const char *default_name);
 
+// Log what backend a subsystem chose, if a hint was set to do so. Useful for debugging.
+extern void SDL_LogBackend(const char *subsystem, const char *backend);
+
 #endif // SDL_utils_h_
diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index 7dc247b193a75..a450bbee841bf 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -1006,7 +1006,9 @@ bool SDL_InitAudio(const char *driver_name)
         }
     }
 
-    if (!initialized) {
+    if (initialized) {
+        SDL_LogBackend("audio", current_audio.name);
+    } else {
         // specific drivers will set the error message if they fail, but otherwise we do it here.
         if (!tried_to_init) {
             if (driver_name) {
diff --git a/src/camera/SDL_camera.c b/src/camera/SDL_camera.c
index 9f71cea0f3d98..7385426afc998 100644
--- a/src/camera/SDL_camera.c
+++ b/src/camera/SDL_camera.c
@@ -1524,7 +1524,9 @@ bool SDL_CameraInit(const char *driver_name)
         }
     }
 
-    if (!initialized) {
+    if (initialized) {
+        SDL_LogBackend("camera", camera_driver.name);
+    } else {
         // specific drivers will set the error message if they fail, but otherwise we do it here.
         if (!tried_to_init) {
             if (driver_name) {
diff --git a/src/gpu/SDL_gpu.c b/src/gpu/SDL_gpu.c
index ebf0b333e9580..3e0002f0581ac 100644
--- a/src/gpu/SDL_gpu.c
+++ b/src/gpu/SDL_gpu.c
@@ -711,6 +711,7 @@ SDL_GPUDevice *SDL_CreateGPUDeviceWithProperties(SDL_PropertiesID props)
 
     selectedBackend = SDL_GPUSelectBackend(props);
     if (selectedBackend != NULL) {
+        SDL_LogBackend("gpu", selectedBackend->name);
         debug_mode = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, true);
         preferLowPower = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, false);
 
diff --git a/src/io/io_uring/SDL_asyncio_liburing.c b/src/io/io_uring/SDL_asyncio_liburing.c
index 07e8c2415ca13..8b4738f9cabcc 100644
--- a/src/io/io_uring/SDL_asyncio_liburing.c
+++ b/src/io/io_uring/SDL_asyncio_liburing.c
@@ -512,10 +512,12 @@ static void MaybeInitializeLibUring(void)
 {
     if (SDL_ShouldInit(&liburing_init)) {
         if (LoadLibUring()) {
+            SDL_LogBackend("asyncio", "liburing");
             CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_liburing;
             QuitAsyncIO = SDL_SYS_QuitAsyncIO_liburing;
             AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_liburing;
         } else {  // can't use liburing? Use the "generic" threadpool implementation instead.
+            SDL_LogBackend("asyncio", "generic");
             CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_Generic;
             QuitAsyncIO = SDL_SYS_QuitAsyncIO_Generic;
             AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_Generic;
diff --git a/src/io/windows/SDL_asyncio_windows_ioring.c b/src/io/windows/SDL_asyncio_windows_ioring.c
index 497456cfa4d22..fd659210150eb 100644
--- a/src/io/windows/SDL_asyncio_windows_ioring.c
+++ b/src/io/windows/SDL_asyncio_windows_ioring.c
@@ -511,10 +511,12 @@ static void MaybeInitializeWinIoRing(void)
 {
     if (SDL_ShouldInit(&ioring_init)) {
         if (LoadWinIoRing()) {
+            SDL_LogBackend("asyncio", "ioring");
             CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_ioring;
             QuitAsyncIO = SDL_SYS_QuitAsyncIO_ioring;
             AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_ioring;
         } else {  // can't use ioring? Use the "generic" threadpool implementation instead.
+            SDL_LogBackend("asyncio", "generic");
             CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_Generic;
             QuitAsyncIO = SDL_SYS_QuitAsyncIO_Generic;
             AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_Generic;
diff --git a/src/power/SDL_power.c b/src/power/SDL_power.c
index 5dde3b145dbe4..3d91c9a931565 100644
--- a/src/power/SDL_power.c
+++ b/src/power/SDL_power.c
@@ -84,7 +84,7 @@ static SDL_GetPowerInfo_Impl implementations[] = {
 SDL_PowerState SDL_GetPowerInfo(int *seconds, int *percent)
 {
 #ifndef SDL_POWER_DISABLED
-    const int total = sizeof(implementations) / sizeof(implementations[0]);
+    const int total = SDL_arraysize(implementations);
     SDL_PowerState result = SDL_POWERSTATE_UNKNOWN;
     int i;
 #endif
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index 60265b81c39f6..3fa627b13fc47 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -1063,7 +1063,9 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
             }
         }
 
-        if (!rc) {
+        if (rc) {
+            SDL_LogBackend("render", renderer->name);
+        } else {
             if (driver_name) {
                 SDL_SetError("%s not available", driver_name);
             } else {
diff --git a/src/storage/SDL_storage.c b/src/storage/SDL_storage.c
index 7c395b36a8e1e..dd3343b0e293b 100644
--- a/src/storage/SDL_storage.c
+++ b/src/storage/SDL_storage.c
@@ -118,7 +118,9 @@ SDL_Storage *SDL_OpenTitleStorage(const char *override, SDL_PropertiesID props)
             }
         }
     }
-    if (!storage) {
+    if (storage) {
+        SDL_LogBackend("title_storage", titlebootstrap[i]->name);
+    } else {
         if (driver_name) {
             SDL_SetError("%s not available", driver_name);
         } else {
@@ -160,7 +162,9 @@ SDL_Storage *SDL_OpenUserStorage(const char *org, const char *app, SDL_Propertie
             }
         }
     }
-    if (!storage) {
+    if (storage) {
+        SDL_LogBackend("user_storage", userbootstrap[i]->name);
+    } else {
         if (driver_name) {
             SDL_SetError("%s not available", driver_name);
         } else {
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index e54a6853965fb..1b29fc42d31ca 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -679,7 +679,9 @@ bool SDL_VideoInit(const char *driver_name)
             }
         }
     }
-    if (!video) {
+    if (video) {
+        SDL_LogBackend("video", bootstrap[i]->name);
+    } else {
         if (driver_name) {
             SDL_SetError("%s not available", driver_name);
             goto pre_driver_error;