SDL: gpu: Add SDL_PROP_GPU_DEVICE_CREATE_VULKAN_REQUIRE_HARDWARE_ACCELERATION property

From b5624e14ffdc88d00e0da2408091e3376c08419b Mon Sep 17 00:00:00 2001
From: Ethan Lee <[EMAIL REDACTED]>
Date: Fri, 7 Nov 2025 14:22:45 -0500
Subject: [PATCH] gpu: Add
 SDL_PROP_GPU_DEVICE_CREATE_VULKAN_REQUIRE_HARDWARE_ACCELERATION property

---
 include/SDL3/SDL_gpu.h          |  9 +++++++++
 src/gpu/vulkan/SDL_gpu_vulkan.c | 14 ++++++++++++++
 2 files changed, 23 insertions(+)

diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h
index 18ee000869629..d3d97b96a747d 100644
--- a/include/SDL3/SDL_gpu.h
+++ b/include/SDL3/SDL_gpu.h
@@ -2307,6 +2307,14 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice(
  *   either supports Tier 2 Resource Binding or does not support D3D12 in any
  *   capacity. Defaults to false.
  *
+ * With the Vulkan renderer:
+ * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_REQUIRE_HARDWARE_ACCELERATION`: By
+ *   default, Vulkan device enumeration includes drivers of all types, including
+ *   software renderers (for example, the Lavapipe Mesa driver). This can be
+ *   useful if your application _requires_ SDL_GPU, but if you can provide your
+ *   own fallback renderer (for example, an OpenGL renderer) this property can
+ *   be set to true. Defaults to false.
+ *
  * \param props the properties to use.
  * \returns a GPU context on success or NULL on failure; call SDL_GetError()
  *          for more information.
@@ -2337,6 +2345,7 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDeviceWithProperties(
 #define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN                     "SDL.gpu.device.create.shaders.metallib"
 #define SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN     "SDL.gpu.device.create.d3d12.allowtier1resourcebinding"
 #define SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING                   "SDL.gpu.device.create.d3d12.semantic"
+#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_REQUIRE_HARDWARE_ACCELERATION         "SDL.gpu.device.create.vulkan.requirehardwareacceleration"
 
 /**
  * Destroys a GPU context previously returned by SDL_CreateGPUDevice.
diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c
index 6dfb9c4624000..3db3e258593ac 100644
--- a/src/gpu/vulkan/SDL_gpu_vulkan.c
+++ b/src/gpu/vulkan/SDL_gpu_vulkan.c
@@ -1096,6 +1096,7 @@ struct VulkanRenderer
 
     bool debugMode;
     bool preferLowPower;
+    bool requireHardwareAcceleration;
     SDL_PropertiesID props;
     Uint32 allowedFramesInFlight;
 
@@ -11343,6 +11344,15 @@ static bool VULKAN_INTERNAL_GetDeviceRank(
         deviceType = physicalDeviceProperties.deviceType;
     }
 
+    if (renderer->requireHardwareAcceleration) {
+        if (deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
+            deviceType != VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU &&
+            deviceType != VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU) {
+            // In addition to CPU, "Other" drivers (including layered drivers) don't count as hardware-accelerated
+            return 0;
+        }
+    }
+
     /* Apply a large bias on the devicePriority so that we always respect the order in the priority arrays.
      * We also rank by e.g. VRAM which should have less influence than the device type.
      */
@@ -11818,6 +11828,8 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props)
         renderer->desiredDeviceFeatures.sampleRateShading = VK_TRUE;
         renderer->desiredDeviceFeatures.imageCubeArray = VK_TRUE;
 
+        renderer->requireHardwareAcceleration = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_REQUIRE_HARDWARE_ACCELERATION, false);
+
         result = VULKAN_INTERNAL_PrepareVulkan(renderer);
         if (result) {
             renderer->vkDestroyInstance(renderer->instance, NULL);
@@ -11867,6 +11879,8 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S
     renderer->desiredDeviceFeatures.sampleRateShading = VK_TRUE;
     renderer->desiredDeviceFeatures.imageCubeArray = VK_TRUE;
 
+    renderer->requireHardwareAcceleration = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_REQUIRE_HARDWARE_ACCELERATION, false);
+
     if (!VULKAN_INTERNAL_PrepareVulkan(renderer)) {
         SET_STRING_ERROR("Failed to initialize Vulkan!");
         SDL_free(renderer);