SDL: gpu: Add SDL_QueryGPUSupport

From 0160e9eac64d7fb47f2e95bd6bd61b1dfc785a81 Mon Sep 17 00:00:00 2001
From: Ethan Lee <[EMAIL REDACTED]>
Date: Fri, 13 Sep 2024 13:23:55 -0400
Subject: [PATCH] gpu: Add SDL_QueryGPUSupport

---
 include/SDL3/SDL_gpu.h            |  32 ++++++++
 src/dynapi/SDL_dynapi.sym         |   2 +
 src/dynapi/SDL_dynapi_overrides.h |   2 +
 src/dynapi/SDL_dynapi_procs.h     |   2 +
 src/gpu/SDL_gpu.c                 | 122 +++++++++++++++++++-----------
 5 files changed, 115 insertions(+), 45 deletions(-)

diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h
index 0f7a239f5ef9d..b905873667616 100644
--- a/include/SDL3/SDL_gpu.h
+++ b/include/SDL3/SDL_gpu.h
@@ -1675,6 +1675,36 @@ typedef struct SDL_GPUStorageTextureWriteOnlyBinding
 
 /* Device */
 
+/**
+ * Checks for GPU runtime support.
+ *
+ * \param format_flags a bitflag indicating which shader formats the app is
+ *                     able to provide.
+ * \param name the preferred GPU driver, or NULL to let SDL pick the optimal
+ *             driver.
+ * \returns SDL_TRUE if supported, SDL_FALSE otherwise.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_CreateGPUDevice
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_QueryGPUSupport(
+    SDL_GPUShaderFormat format_flags,
+    const char *name);
+
+/**
+ * Checks for GPU runtime support.
+ *
+ * \param props the properties to use.
+ * \returns SDL_TRUE if supported, SDL_FALSE otherwise.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_CreateGPUDeviceWithProperties
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_QueryGPUSupportWithProperties(
+    SDL_PropertiesID props);
+
 /**
  * Creates a GPU context.
  *
@@ -1690,6 +1720,7 @@ typedef struct SDL_GPUStorageTextureWriteOnlyBinding
  * \sa SDL_GetGPUShaderFormats
  * \sa SDL_GetGPUDeviceDriver
  * \sa SDL_DestroyGPUDevice
+ * \sa SDL_QueryGPUSupport
  */
 extern SDL_DECLSPEC SDL_GPUDevice *SDLCALL SDL_CreateGPUDevice(
     SDL_GPUShaderFormat format_flags,
@@ -1736,6 +1767,7 @@ extern SDL_DECLSPEC SDL_GPUDevice *SDLCALL SDL_CreateGPUDevice(
  * \sa SDL_GetGPUShaderFormats
  * \sa SDL_GetGPUDeviceDriver
  * \sa SDL_DestroyGPUDevice
+ * \sa SDL_QueryGPUSupportWithProperties
  */
 extern SDL_DECLSPEC SDL_GPUDevice *SDLCALL SDL_CreateGPUDeviceWithProperties(
     SDL_PropertiesID props);
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 89e3992addd02..1b5f1a40a3bf5 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -669,6 +669,8 @@ SDL3_0.0.0 {
     SDL_PushGPUVertexUniformData;
     SDL_PutAudioStreamData;
     SDL_QueryGPUFence;
+    SDL_QueryGPUSupport;
+    SDL_QueryGPUSupportWithProperties;
     SDL_Quit;
     SDL_QuitSubSystem;
     SDL_RaiseWindow;
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 9bd82e2026be8..8bad28dbdee6c 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -694,6 +694,8 @@
 #define SDL_PushGPUVertexUniformData SDL_PushGPUVertexUniformData_REAL
 #define SDL_PutAudioStreamData SDL_PutAudioStreamData_REAL
 #define SDL_QueryGPUFence SDL_QueryGPUFence_REAL
+#define SDL_QueryGPUSupport SDL_QueryGPUSupport_REAL
+#define SDL_QueryGPUSupportWithProperties SDL_QueryGPUSupportWithProperties_REAL
 #define SDL_Quit SDL_Quit_REAL
 #define SDL_QuitSubSystem SDL_QuitSubSystem_REAL
 #define SDL_RaiseWindow SDL_RaiseWindow_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 0ad90cfb85d16..250089ab4c216 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -705,6 +705,8 @@ SDL_DYNAPI_PROC(void,SDL_PushGPUFragmentUniformData,(SDL_GPUCommandBuffer *a, Ui
 SDL_DYNAPI_PROC(void,SDL_PushGPUVertexUniformData,(SDL_GPUCommandBuffer *a, Uint32 b, const void *c, Uint32 d),(a,b,c,d),)
 SDL_DYNAPI_PROC(SDL_bool,SDL_PutAudioStreamData,(SDL_AudioStream *a, const void *b, int c),(a,b,c),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_QueryGPUFence,(SDL_GPUDevice *a, SDL_GPUFence *b),(a,b),return)
+SDL_DYNAPI_PROC(SDL_bool,SDL_QueryGPUSupport,(SDL_GPUShaderFormat a, const char *b),(a,b),return)
+SDL_DYNAPI_PROC(SDL_bool,SDL_QueryGPUSupportWithProperties,(SDL_PropertiesID a),(a),return)
 SDL_DYNAPI_PROC(void,SDL_Quit,(void),(),)
 SDL_DYNAPI_PROC(void,SDL_QuitSubSystem,(SDL_InitFlags a),(a),)
 SDL_DYNAPI_PROC(SDL_bool,SDL_RaiseWindow,(SDL_Window *a),(a),return)
diff --git a/src/gpu/SDL_gpu.c b/src/gpu/SDL_gpu.c
index 7a40303449afe..fef2c982225d9 100644
--- a/src/gpu/SDL_gpu.c
+++ b/src/gpu/SDL_gpu.c
@@ -371,12 +371,41 @@ void SDL_GPU_BlitCommon(
 // Driver Functions
 
 #ifndef SDL_GPU_DISABLED
-static const SDL_GPUBootstrap * SDL_GPUSelectBackend(
-    SDL_VideoDevice *_this,
-    const char *gpudriver,
-    SDL_GPUShaderFormat format_flags)
+static const SDL_GPUBootstrap * SDL_GPUSelectBackend(SDL_PropertiesID props)
 {
     Uint32 i;
+    SDL_GPUShaderFormat format_flags = 0;
+    const char *gpudriver;
+    SDL_VideoDevice *_this = SDL_GetVideoDevice();
+
+    if (_this == NULL) {
+        SDL_SetError("Video subsystem not initialized");
+        return NULL;
+    }
+
+    if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_PRIVATE_BOOL, false)) {
+        format_flags |= SDL_GPU_SHADERFORMAT_PRIVATE;
+    }
+    if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOL, false)) {
+        format_flags |= SDL_GPU_SHADERFORMAT_SPIRV;
+    }
+    if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOL, false)) {
+        format_flags |= SDL_GPU_SHADERFORMAT_DXBC;
+    }
+    if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOL, false)) {
+        format_flags |= SDL_GPU_SHADERFORMAT_DXIL;
+    }
+    if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOL, false)) {
+        format_flags |= SDL_GPU_SHADERFORMAT_MSL;
+    }
+    if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOL, false)) {
+        format_flags |= SDL_GPU_SHADERFORMAT_METALLIB;
+    }
+
+    gpudriver = SDL_GetHint(SDL_HINT_GPU_DRIVER);
+    if (gpudriver == NULL) {
+        gpudriver = SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, NULL);
+    }
 
     // Environment/Properties override...
     if (gpudriver != NULL) {
@@ -411,14 +440,12 @@ static const SDL_GPUBootstrap * SDL_GPUSelectBackend(
 }
 #endif // SDL_GPU_DISABLED
 
-SDL_GPUDevice *SDL_CreateGPUDevice(
+static void SDL_GPU_FillProperties(
+    SDL_PropertiesID props,
     SDL_GPUShaderFormat format_flags,
     SDL_bool debug_mode,
     const char *name)
 {
-#ifndef SDL_GPU_DISABLED
-    SDL_GPUDevice *result;
-    SDL_PropertiesID props = SDL_CreateProperties();
     if (format_flags & SDL_GPU_SHADERFORMAT_PRIVATE) {
         SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_PRIVATE_BOOL, true);
     }
@@ -439,6 +466,44 @@ SDL_GPUDevice *SDL_CreateGPUDevice(
     }
     SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOL, debug_mode);
     SDL_SetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, name);
+}
+
+SDL_bool SDL_QueryGPUSupport(
+    SDL_GPUShaderFormat format_flags,
+    const char *name)
+{
+#ifndef SDL_GPU_DISABLED
+    bool result;
+    SDL_PropertiesID props = SDL_CreateProperties();
+    SDL_GPU_FillProperties(props, format_flags, SDL_FALSE, name);
+    result = SDL_QueryGPUSupportWithProperties(props);
+    SDL_DestroyProperties(props);
+    return result;
+#else
+    SDL_SetError("SDL not built with GPU support");
+    return SDL_FALSE;
+#endif
+}
+
+SDL_bool SDL_QueryGPUSupportWithProperties(SDL_PropertiesID props)
+{
+#ifndef SDL_GPU_DISABLED
+    return (SDL_GPUSelectBackend(props) != NULL);
+#else
+    SDL_SetError("SDL not built with GPU support");
+    return SDL_FALSE;
+#endif
+}
+
+SDL_GPUDevice *SDL_CreateGPUDevice(
+    SDL_GPUShaderFormat format_flags,
+    SDL_bool debug_mode,
+    const char *name)
+{
+#ifndef SDL_GPU_DISABLED
+    SDL_GPUDevice *result;
+    SDL_PropertiesID props = SDL_CreateProperties();
+    SDL_GPU_FillProperties(props, format_flags, debug_mode, name);
     result = SDL_CreateGPUDeviceWithProperties(props);
     SDL_DestroyProperties(props);
     return result;
@@ -451,49 +516,16 @@ SDL_GPUDevice *SDL_CreateGPUDevice(
 SDL_GPUDevice *SDL_CreateGPUDeviceWithProperties(SDL_PropertiesID props)
 {
 #ifndef SDL_GPU_DISABLED
-    SDL_GPUShaderFormat format_flags = 0;
     bool debug_mode;
     bool preferLowPower;
-
-    const char *gpudriver;
     SDL_GPUDevice *result = NULL;
     const SDL_GPUBootstrap *selectedBackend;
-    SDL_VideoDevice *_this = SDL_GetVideoDevice();
-
-    if (_this == NULL) {
-        SDL_SetError("Video subsystem not initialized");
-        return NULL;
-    }
-
-    if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_PRIVATE_BOOL, false)) {
-        format_flags |= SDL_GPU_SHADERFORMAT_PRIVATE;
-    }
-    if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOL, false)) {
-        format_flags |= SDL_GPU_SHADERFORMAT_SPIRV;
-    }
-    if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOL, false)) {
-        format_flags |= SDL_GPU_SHADERFORMAT_DXBC;
-    }
-    if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOL, false)) {
-        format_flags |= SDL_GPU_SHADERFORMAT_DXIL;
-    }
-    if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOL, false)) {
-        format_flags |= SDL_GPU_SHADERFORMAT_MSL;
-    }
-    if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOL, false)) {
-        format_flags |= SDL_GPU_SHADERFORMAT_METALLIB;
-    }
-
-    debug_mode = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOL, true);
-    preferLowPower = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOL, false);
 
-    gpudriver = SDL_GetHint(SDL_HINT_GPU_DRIVER);
-    if (gpudriver == NULL) {
-        gpudriver = SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, NULL);
-    }
-
-    selectedBackend = SDL_GPUSelectBackend(_this, gpudriver, format_flags);
+    selectedBackend = SDL_GPUSelectBackend(props);
     if (selectedBackend != NULL) {
+        debug_mode = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOL, true);
+        preferLowPower = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOL, false);
+
         result = selectedBackend->CreateDevice(debug_mode, preferLowPower, props);
         if (result != NULL) {
             result->backend = selectedBackend->name;