SDL: Add SDL_Vulkan_DestroySurface functionality (#9817)

From f17b556c76df6b41272a7038dee75c5cdfd192e3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ali=20Emre=20G=C3=BClc=C3=BC?= <[EMAIL REDACTED]>
Date: Fri, 17 May 2024 03:41:15 +0300
Subject: [PATCH] Add SDL_Vulkan_DestroySurface functionality (#9817)

Added SDL_Vulkan_DestroySurface, its documentation and corresponding platform specific implementations. Fixed some header inclusion orders to improve consistency between platforms. Added TODOs regarding MetalView creation and destruction which will benefit from the new functionality.
---
 include/SDL3/SDL_vulkan.h             | 28 +++++++++++++++++++++++++++
 src/dynapi/SDL_dynapi.sym             |  1 +
 src/dynapi/SDL_dynapi_overrides.h     |  1 +
 src/dynapi/SDL_dynapi_procs.h         |  1 +
 src/video/SDL_sysvideo.h              |  1 +
 src/video/SDL_video.c                 |  9 +++++++++
 src/video/SDL_vulkan_internal.h       | 11 +++++++++++
 src/video/SDL_vulkan_utils.c          | 17 ++++++++++++++++
 src/video/android/SDL_androidvideo.c  |  1 +
 src/video/android/SDL_androidvulkan.c | 10 ++++++++++
 src/video/android/SDL_androidvulkan.h |  6 +++++-
 src/video/cocoa/SDL_cocoavideo.m      |  1 +
 src/video/cocoa/SDL_cocoavulkan.h     |  4 ++++
 src/video/cocoa/SDL_cocoavulkan.m     | 16 ++++++++++++++-
 src/video/kmsdrm/SDL_kmsdrmvideo.c    |  1 +
 src/video/kmsdrm/SDL_kmsdrmvulkan.c   | 10 ++++++++++
 src/video/kmsdrm/SDL_kmsdrmvulkan.h   |  6 +++++-
 src/video/uikit/SDL_uikitvideo.m      |  1 +
 src/video/uikit/SDL_uikitvulkan.h     |  6 +++++-
 src/video/uikit/SDL_uikitvulkan.m     | 16 ++++++++++++++-
 src/video/vivante/SDL_vivantevideo.c  |  1 +
 src/video/vivante/SDL_vivantevulkan.c | 10 ++++++++++
 src/video/vivante/SDL_vivantevulkan.h |  6 +++++-
 src/video/wayland/SDL_waylandvideo.c  |  1 +
 src/video/wayland/SDL_waylandvulkan.c | 10 ++++++++++
 src/video/wayland/SDL_waylandvulkan.h |  8 ++++++--
 src/video/windows/SDL_windowsvideo.c  |  1 +
 src/video/windows/SDL_windowsvulkan.c | 10 ++++++++++
 src/video/windows/SDL_windowsvulkan.h | 10 +++++++---
 src/video/x11/SDL_x11video.c          |  1 +
 src/video/x11/SDL_x11vulkan.c         | 10 ++++++++++
 src/video/x11/SDL_x11vulkan.h         |  9 +++++++--
 32 files changed, 211 insertions(+), 13 deletions(-)

diff --git a/include/SDL3/SDL_vulkan.h b/include/SDL3/SDL_vulkan.h
index 7871a05c80fb6..9a41cb8eaad77 100644
--- a/include/SDL3/SDL_vulkan.h
+++ b/include/SDL3/SDL_vulkan.h
@@ -177,12 +177,40 @@ extern DECLSPEC char const* const* SDLCALL SDL_Vulkan_GetInstanceExtensions(Uint
  * \since This function is available since SDL 3.0.0.
  *
  * \sa SDL_Vulkan_GetInstanceExtensions
+ * \sa SDL_Vulkan_DestroySurface
  */
 extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_CreateSurface(SDL_Window *window,
                                                           VkInstance instance,
                                                           const struct VkAllocationCallbacks *allocator,
                                                           VkSurfaceKHR* surface);
 
+/**
+ * Destroy the Vulkan rendering surface of a window.
+ *
+ * This should be called before SDL_DestroyWindow, if SDL_Vulkan_CreateSurface
+ * was called after SDL_CreateWindow.
+ *
+ * The `instance` must have been created with extensions returned by
+ * SDL_Vulkan_GetInstanceExtensions() enabled and `surface` must have been
+ * created successfully by an SDL_Vulkan_CreateSurface() call.
+ *
+ * If `allocator` is NULL, Vulkan will use the system default allocator. This
+ * argument is passed directly to Vulkan and isn't used by SDL itself.
+ *
+ * \param instance The Vulkan instance handle
+ * \param surface VkSurfaceKHR handle to destroy
+ * \param allocator A VkAllocationCallbacks struct, which lets the app set the
+ *                  allocator that destroys the surface. Can be NULL.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_Vulkan_GetInstanceExtensions
+ * \sa SDL_Vulkan_CreateSurface
+ */
+extern DECLSPEC void SDLCALL SDL_Vulkan_DestroySurface(VkInstance instance,
+                                                       VkSurfaceKHR surface,
+                                                       const struct VkAllocationCallbacks *allocator);
+
 /* @} *//* Vulkan support functions */
 
 /* Ends C function definitions when using C++ */
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index f4753e31a5cf7..1c647630e9c0a 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -825,6 +825,7 @@ SDL3_0.0.0 {
     SDL_UpdateWindowSurfaceRects;
     SDL_UpdateYUVTexture;
     SDL_Vulkan_CreateSurface;
+    SDL_Vulkan_DestroySurface;
     SDL_Vulkan_GetInstanceExtensions;
     SDL_Vulkan_GetVkGetInstanceProcAddr;
     SDL_Vulkan_LoadLibrary;
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 3ee09741fba02..b54048d1bff78 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -849,6 +849,7 @@
 #define SDL_UpdateWindowSurfaceRects SDL_UpdateWindowSurfaceRects_REAL
 #define SDL_UpdateYUVTexture SDL_UpdateYUVTexture_REAL
 #define SDL_Vulkan_CreateSurface SDL_Vulkan_CreateSurface_REAL
+#define SDL_Vulkan_DestroySurface SDL_Vulkan_DestroySurface_REAL
 #define SDL_Vulkan_GetInstanceExtensions SDL_Vulkan_GetInstanceExtensions_REAL
 #define SDL_Vulkan_GetVkGetInstanceProcAddr SDL_Vulkan_GetVkGetInstanceProcAddr_REAL
 #define SDL_Vulkan_LoadLibrary SDL_Vulkan_LoadLibrary_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 37f0687f2cc95..4d85f53e95041 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -869,6 +869,7 @@ SDL_DYNAPI_PROC(int,SDL_UpdateWindowSurface,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_UpdateWindowSurfaceRects,(SDL_Window *a, const SDL_Rect *b, int c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_UpdateYUVTexture,(SDL_Texture *a, const SDL_Rect *b, const Uint8 *c, int d, const Uint8 *e, int f, const Uint8 *g, int h),(a,b,c,d,e,f,g,h),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_Vulkan_CreateSurface,(SDL_Window *a, VkInstance b, const struct VkAllocationCallbacks *c, VkSurfaceKHR *d),(a,b,c,d),return)
+SDL_DYNAPI_PROC(void,SDL_Vulkan_DestroySurface,(VkInstance a, VkSurfaceKHR b, const struct VkAllocationCallbacks *c),(a,b,c),)
 SDL_DYNAPI_PROC(char const* const*,SDL_Vulkan_GetInstanceExtensions,(Uint32 *a),(a),return)
 SDL_DYNAPI_PROC(SDL_FunctionPointer,SDL_Vulkan_GetVkGetInstanceProcAddr,(void),(),return)
 SDL_DYNAPI_PROC(int,SDL_Vulkan_LoadLibrary,(const char *a),(a),return)
diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h
index bfc85a510521e..7cf30ade20778 100644
--- a/src/video/SDL_sysvideo.h
+++ b/src/video/SDL_sysvideo.h
@@ -285,6 +285,7 @@ struct SDL_VideoDevice
     void (*Vulkan_UnloadLibrary)(SDL_VideoDevice *_this);
     char const* const* (*Vulkan_GetInstanceExtensions)(SDL_VideoDevice *_this, Uint32 *count);
     SDL_bool (*Vulkan_CreateSurface)(SDL_VideoDevice *_this, SDL_Window *window, VkInstance instance, const struct VkAllocationCallbacks *allocator, VkSurfaceKHR *surface);
+    void (*Vulkan_DestroySurface)(SDL_VideoDevice *_this, VkInstance instance, VkSurfaceKHR surface, const struct VkAllocationCallbacks *allocator);
 
     /* * * */
     /*
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index fdff61e28543b..f77e24add6caf 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -5344,6 +5344,15 @@ SDL_bool SDL_Vulkan_CreateSurface(SDL_Window *window,
     return _this->Vulkan_CreateSurface(_this, window, instance, allocator, surface);
 }
 
+void SDL_Vulkan_DestroySurface(VkInstance instance,
+                               VkSurfaceKHR surface,
+                               const struct VkAllocationCallbacks *allocator)
+{
+    if (_this && instance && surface && _this->Vulkan_DestroySurface) {
+        _this->Vulkan_DestroySurface(_this, instance, surface, allocator);
+    }
+}
+
 SDL_MetalView SDL_Metal_CreateView(SDL_Window *window)
 {
     CHECK_WINDOW_MAGIC(window, NULL);
diff --git a/src/video/SDL_vulkan_internal.h b/src/video/SDL_vulkan_internal.h
index 5961df84b933e..cd3b6e89080bb 100644
--- a/src/video/SDL_vulkan_internal.h
+++ b/src/video/SDL_vulkan_internal.h
@@ -67,6 +67,17 @@ extern SDL_bool SDL_Vulkan_Display_CreateSurface(void *vkGetInstanceProcAddr,
                                                  VkInstance instance,
                                                  const struct VkAllocationCallbacks *allocator,
                                                  VkSurfaceKHR *surface);
+
+/* Platform independent base function for destroying the Vulkan surface. Unlike surface
+ * creation, surface destruction doesn't require platform specific extensions like
+ * VK_KHR_wayland_surface, VK_KHR_android_surface or VK_EXT_metal_surface. The only
+ * necessary extension is cross platform VK_KHR_surface, which is a dependency to all
+ * WSI platform extensions, so we can handle surface destruction in an platform-independent
+ * manner. */
+extern void SDL_Vulkan_DestroySurface_Internal(void *vkGetInstanceProcAddr,
+                                               VkInstance instance,
+                                               VkSurfaceKHR surface,
+                                               const struct VkAllocationCallbacks *allocator);
 #else
 
 /* No SDL Vulkan support, just include the header for typedefs */
diff --git a/src/video/SDL_vulkan_utils.c b/src/video/SDL_vulkan_utils.c
index 53ec439ac5612..216cd140501b6 100644
--- a/src/video/SDL_vulkan_utils.c
+++ b/src/video/SDL_vulkan_utils.c
@@ -467,4 +467,21 @@ SDL_bool SDL_Vulkan_Display_CreateSurface(void *vkGetInstanceProcAddr_,
     return SDL_FALSE;
 }
 
+void SDL_Vulkan_DestroySurface_Internal(void *vkGetInstanceProcAddr_,
+                                        VkInstance instance,
+                                        VkSurfaceKHR surface,
+                                        const struct VkAllocationCallbacks *allocator)
+{
+    PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr =
+        (PFN_vkGetInstanceProcAddr)vkGetInstanceProcAddr_;
+    PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR =
+        (PFN_vkDestroySurfaceKHR)vkGetInstanceProcAddr(
+            instance,
+            "vkDestroySurfaceKHR");
+
+    if (vkDestroySurfaceKHR) {
+        vkDestroySurfaceKHR(instance, surface, allocator);
+    }
+}
+
 #endif
diff --git a/src/video/android/SDL_androidvideo.c b/src/video/android/SDL_androidvideo.c
index 17f7e57048f64..5924a027ecdd9 100644
--- a/src/video/android/SDL_androidvideo.c
+++ b/src/video/android/SDL_androidvideo.c
@@ -137,6 +137,7 @@ static SDL_VideoDevice *Android_CreateDevice(void)
     device->Vulkan_UnloadLibrary = Android_Vulkan_UnloadLibrary;
     device->Vulkan_GetInstanceExtensions = Android_Vulkan_GetInstanceExtensions;
     device->Vulkan_CreateSurface = Android_Vulkan_CreateSurface;
+    device->Vulkan_DestroySurface = Android_Vulkan_DestroySurface;
 #endif
 
     /* Screensaver */
diff --git a/src/video/android/SDL_androidvulkan.c b/src/video/android/SDL_androidvulkan.c
index 14c391a64da03..b23be101951ea 100644
--- a/src/video/android/SDL_androidvulkan.c
+++ b/src/video/android/SDL_androidvulkan.c
@@ -162,4 +162,14 @@ SDL_bool Android_Vulkan_CreateSurface(SDL_VideoDevice *_this,
     return SDL_TRUE;
 }
 
+void Android_Vulkan_DestroySurface(SDL_VideoDevice *_this,
+                                   VkInstance instance,
+                                   VkSurfaceKHR surface,
+                                   const struct VkAllocationCallbacks *allocator)
+{
+    if (_this->vulkan_config.loader_handle) {
+        SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator);
+    }
+}
+
 #endif
diff --git a/src/video/android/SDL_androidvulkan.h b/src/video/android/SDL_androidvulkan.h
index 314c5d878840e..eed91490761e9 100644
--- a/src/video/android/SDL_androidvulkan.h
+++ b/src/video/android/SDL_androidvulkan.h
@@ -37,12 +37,16 @@
 int Android_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path);
 void Android_Vulkan_UnloadLibrary(SDL_VideoDevice *_this);
 char const* const* Android_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this,
-                                              Uint32 *count);
+                                                        Uint32 *count);
 SDL_bool Android_Vulkan_CreateSurface(SDL_VideoDevice *_this,
                                       SDL_Window *window,
                                       VkInstance instance,
                                       const struct VkAllocationCallbacks *allocator,
                                       VkSurfaceKHR *surface);
+void Android_Vulkan_DestroySurface(SDL_VideoDevice *_this,
+                                   VkInstance instance,
+                                   VkSurfaceKHR surface,
+                                   const struct VkAllocationCallbacks *allocator);
 
 #endif
 
diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m
index 385d0eb978205..5e796c73f4b29 100644
--- a/src/video/cocoa/SDL_cocoavideo.m
+++ b/src/video/cocoa/SDL_cocoavideo.m
@@ -161,6 +161,7 @@ static void Cocoa_DeleteDevice(SDL_VideoDevice *device)
         device->Vulkan_UnloadLibrary = Cocoa_Vulkan_UnloadLibrary;
         device->Vulkan_GetInstanceExtensions = Cocoa_Vulkan_GetInstanceExtensions;
         device->Vulkan_CreateSurface = Cocoa_Vulkan_CreateSurface;
+        device->Vulkan_DestroySurface = Cocoa_Vulkan_DestroySurface;
 #endif
 
 #ifdef SDL_VIDEO_METAL
diff --git a/src/video/cocoa/SDL_cocoavulkan.h b/src/video/cocoa/SDL_cocoavulkan.h
index 2d94c179a2a94..158cf378fad53 100644
--- a/src/video/cocoa/SDL_cocoavulkan.h
+++ b/src/video/cocoa/SDL_cocoavulkan.h
@@ -43,6 +43,10 @@ SDL_bool Cocoa_Vulkan_CreateSurface(SDL_VideoDevice *_this,
                                     VkInstance instance,
                                     const struct VkAllocationCallbacks *allocator,
                                     VkSurfaceKHR *surface);
+void Cocoa_Vulkan_DestroySurface(SDL_VideoDevice *_this,
+                                 VkInstance instance,
+                                 VkSurfaceKHR surface,
+                                 const struct VkAllocationCallbacks *allocator);
 
 #endif
 
diff --git a/src/video/cocoa/SDL_cocoavulkan.m b/src/video/cocoa/SDL_cocoavulkan.m
index edcfa441e1a88..f54164316e70a 100644
--- a/src/video/cocoa/SDL_cocoavulkan.m
+++ b/src/video/cocoa/SDL_cocoavulkan.m
@@ -221,7 +221,10 @@ static SDL_bool Cocoa_Vulkan_CreateSurfaceViaMetalView(SDL_VideoDevice *_this,
      * Metal_DestroyView from. Right now the metal view's ref count is +2 (one
      * from returning a new view object in CreateView, and one because it's
      * a subview of the window.) If we release the view here to make it +1, it
-     * will be destroyed when the window is destroyed. */
+     * will be destroyed when the window is destroyed.
+     *
+     * TODO: Now that we have SDL_Vulkan_DestroySurface someone with enough
+     * knowledge of Metal can proceed. */
     CFBridgingRelease(metalview);
 
     return SDL_TRUE;
@@ -297,4 +300,15 @@ SDL_bool Cocoa_Vulkan_CreateSurface(SDL_VideoDevice *_this,
     return SDL_TRUE;
 }
 
+void Cocoa_Vulkan_DestroySurface(SDL_VideoDevice *_this,
+                                 VkInstance instance,
+                                 VkSurfaceKHR surface,
+                                 const struct VkAllocationCallbacks *allocator)
+{
+    if (_this->vulkan_config.loader_handle) {
+        SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator);
+        /* TODO: Add CFBridgingRelease(metalview) here perhaps? */
+    }
+}
+
 #endif
diff --git a/src/video/kmsdrm/SDL_kmsdrmvideo.c b/src/video/kmsdrm/SDL_kmsdrmvideo.c
index a2837b7620a75..2df6d29ae3c88 100644
--- a/src/video/kmsdrm/SDL_kmsdrmvideo.c
+++ b/src/video/kmsdrm/SDL_kmsdrmvideo.c
@@ -310,6 +310,7 @@ static SDL_VideoDevice *KMSDRM_CreateDevice(void)
     device->Vulkan_UnloadLibrary = KMSDRM_Vulkan_UnloadLibrary;
     device->Vulkan_GetInstanceExtensions = KMSDRM_Vulkan_GetInstanceExtensions;
     device->Vulkan_CreateSurface = KMSDRM_Vulkan_CreateSurface;
+    device->Vulkan_DestroySurface = KMSDRM_Vulkan_DestroySurface;
 #endif
 
     device->PumpEvents = KMSDRM_PumpEvents;
diff --git a/src/video/kmsdrm/SDL_kmsdrmvulkan.c b/src/video/kmsdrm/SDL_kmsdrmvulkan.c
index b0cb1347535d8..c41456c05ae2b 100644
--- a/src/video/kmsdrm/SDL_kmsdrmvulkan.c
+++ b/src/video/kmsdrm/SDL_kmsdrmvulkan.c
@@ -505,4 +505,14 @@ SDL_bool KMSDRM_Vulkan_CreateSurface(SDL_VideoDevice *_this,
     return ret;
 }
 
+void KMSDRM_Vulkan_DestroySurface(SDL_VideoDevice *_this,
+                                  VkInstance instance,
+                                  VkSurfaceKHR surface,
+                                  const struct VkAllocationCallbacks *allocator)
+{
+    if (_this->vulkan_config.loader_handle) {
+        SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator);
+    }
+}
+
 #endif
diff --git a/src/video/kmsdrm/SDL_kmsdrmvulkan.h b/src/video/kmsdrm/SDL_kmsdrmvulkan.h
index b29cbe8353b81..b8dfd591a782b 100644
--- a/src/video/kmsdrm/SDL_kmsdrmvulkan.h
+++ b/src/video/kmsdrm/SDL_kmsdrmvulkan.h
@@ -37,12 +37,16 @@
 int KMSDRM_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path);
 void KMSDRM_Vulkan_UnloadLibrary(SDL_VideoDevice *_this);
 char const* const* KMSDRM_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this,
-                                             Uint32 *count);
+                                                       Uint32 *count);
 SDL_bool KMSDRM_Vulkan_CreateSurface(SDL_VideoDevice *_this,
                                      SDL_Window *window,
                                      VkInstance instance,
                                      const struct VkAllocationCallbacks *allocator,
                                      VkSurfaceKHR *surface);
+void KMSDRM_Vulkan_DestroySurface(SDL_VideoDevice *_this,
+                                  VkInstance instance,
+                                  VkSurfaceKHR surface,
+                                  const struct VkAllocationCallbacks *allocator);
 
 #endif
 
diff --git a/src/video/uikit/SDL_uikitvideo.m b/src/video/uikit/SDL_uikitvideo.m
index 7776e3037c186..c4f1eb04609a5 100644
--- a/src/video/uikit/SDL_uikitvideo.m
+++ b/src/video/uikit/SDL_uikitvideo.m
@@ -123,6 +123,7 @@ static void UIKit_DeleteDevice(SDL_VideoDevice *device)
         device->Vulkan_UnloadLibrary = UIKit_Vulkan_UnloadLibrary;
         device->Vulkan_GetInstanceExtensions = UIKit_Vulkan_GetInstanceExtensions;
         device->Vulkan_CreateSurface = UIKit_Vulkan_CreateSurface;
+        device->Vulkan_DestroySurface = UIKit_Vulkan_DestroySurface;
 #endif
 
 #ifdef SDL_VIDEO_METAL
diff --git a/src/video/uikit/SDL_uikitvulkan.h b/src/video/uikit/SDL_uikitvulkan.h
index 385fdfc02f1ea..71509708a9b04 100644
--- a/src/video/uikit/SDL_uikitvulkan.h
+++ b/src/video/uikit/SDL_uikitvulkan.h
@@ -37,12 +37,16 @@
 int UIKit_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path);
 void UIKit_Vulkan_UnloadLibrary(SDL_VideoDevice *_this);
 char const* const* UIKit_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this,
-                                            Uint32 *count);
+                                                      Uint32 *count);
 SDL_bool UIKit_Vulkan_CreateSurface(SDL_VideoDevice *_this,
                                     SDL_Window *window,
                                     VkInstance instance,
                                     const struct VkAllocationCallbacks *allocator,
                                     VkSurfaceKHR *surface);
+void UIKit_Vulkan_DestroySurface(SDL_VideoDevice *_this,
+                                 VkInstance instance,
+                                 VkSurfaceKHR surface,
+                                 const struct VkAllocationCallbacks *allocator);
 
 #endif
 
diff --git a/src/video/uikit/SDL_uikitvulkan.m b/src/video/uikit/SDL_uikitvulkan.m
index f2d00220ffca9..9d624843bce05 100644
--- a/src/video/uikit/SDL_uikitvulkan.m
+++ b/src/video/uikit/SDL_uikitvulkan.m
@@ -248,10 +248,24 @@ SDL_bool UIKit_Vulkan_CreateSurface(SDL_VideoDevice *_this,
      * Metal_DestroyView from. Right now the metal view's ref count is +2 (one
      * from returning a new view object in CreateView, and one because it's
      * a subview of the window.) If we release the view here to make it +1, it
-     * will be destroyed when the window is destroyed. */
+     * will be destroyed when the window is destroyed.
+     *
+     * TODO: Now that we have SDL_Vulkan_DestroySurface someone with enough
+     * knowledge of Metal can proceed. */
     CFBridgingRelease(metalview);
 
     return SDL_TRUE;
 }
 
+void UIKit_Vulkan_DestroySurface(SDL_VideoDevice *_this,
+                                 VkInstance instance,
+                                 VkSurfaceKHR surface,
+                                 const struct VkAllocationCallbacks *allocator)
+{
+    if (_this->vulkan_config.loader_handle) {
+        SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator);
+        /* TODO: Add CFBridgingRelease(metalview) here perhaps? */
+    }
+}
+
 #endif
diff --git a/src/video/vivante/SDL_vivantevideo.c b/src/video/vivante/SDL_vivantevideo.c
index 6778f472b3107..c808cf8786a7c 100644
--- a/src/video/vivante/SDL_vivantevideo.c
+++ b/src/video/vivante/SDL_vivantevideo.c
@@ -95,6 +95,7 @@ static SDL_VideoDevice *VIVANTE_Create()
     device->Vulkan_UnloadLibrary = VIVANTE_Vulkan_UnloadLibrary;
     device->Vulkan_GetInstanceExtensions = VIVANTE_Vulkan_GetInstanceExtensions;
     device->Vulkan_CreateSurface = VIVANTE_Vulkan_CreateSurface;
+    device->Vulkan_DestroySurface = VIVANTE_Vulkan_DestroySurface;
 #endif
 
     device->PumpEvents = VIVANTE_PumpEvents;
diff --git a/src/video/vivante/SDL_vivantevulkan.c b/src/video/vivante/SDL_vivantevulkan.c
index cc99edcf2ad27..752a64860e9dc 100644
--- a/src/video/vivante/SDL_vivantevulkan.c
+++ b/src/video/vivante/SDL_vivantevulkan.c
@@ -142,4 +142,14 @@ SDL_bool VIVANTE_Vulkan_CreateSurface(SDL_VideoDevice *_this,
     return SDL_Vulkan_Display_CreateSurface(_this->vulkan_config.vkGetInstanceProcAddr, instance, allocator, surface);
 }
 
+void VIVANTE_Vulkan_DestroySurface(SDL_VideoDevice *_this,
+                                   VkInstance instance,
+                                   VkSurfaceKHR surface,
+                                   const struct VkAllocationCallbacks *allocator)
+{
+    if (_this->vulkan_config.loader_handle) {
+        SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator);
+    }
+}
+
 #endif
diff --git a/src/video/vivante/SDL_vivantevulkan.h b/src/video/vivante/SDL_vivantevulkan.h
index 9aa566b20ee26..b73ad549e27f1 100644
--- a/src/video/vivante/SDL_vivantevulkan.h
+++ b/src/video/vivante/SDL_vivantevulkan.h
@@ -36,12 +36,16 @@
 int VIVANTE_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path);
 void VIVANTE_Vulkan_UnloadLibrary(SDL_VideoDevice *_this);
 char const* const* VIVANTE_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this,
-                                              Uint32 *count);
+                                                        Uint32 *count);
 SDL_bool VIVANTE_Vulkan_CreateSurface(SDL_VideoDevice *_this,
                                       SDL_Window *window,
                                       VkInstance instance,
                                       const struct VkAllocationCallbacks *allocator,
                                       VkSurfaceKHR *surface);
+void VIVANTE_Vulkan_DestroySurface(SDL_VideoDevice *_this,
+                                   VkInstance instance,
+                                   VkSurfaceKHR surface,
+                                   const struct VkAllocationCallbacks *allocator);
 
 #endif
 
diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c
index f8db42975c965..5e94d4f44ab82 100644
--- a/src/video/wayland/SDL_waylandvideo.c
+++ b/src/video/wayland/SDL_waylandvideo.c
@@ -515,6 +515,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(void)
     device->Vulkan_UnloadLibrary = Wayland_Vulkan_UnloadLibrary;
     device->Vulkan_GetInstanceExtensions = Wayland_Vulkan_GetInstanceExtensions;
     device->Vulkan_CreateSurface = Wayland_Vulkan_CreateSurface;
+    device->Vulkan_DestroySurface = Wayland_Vulkan_DestroySurface;
 #endif
 
     device->free = Wayland_DeleteDevice;
diff --git a/src/video/wayland/SDL_waylandvulkan.c b/src/video/wayland/SDL_waylandvulkan.c
index 5b69090520cef..2c6950a2b7410 100644
--- a/src/video/wayland/SDL_waylandvulkan.c
+++ b/src/video/wayland/SDL_waylandvulkan.c
@@ -169,4 +169,14 @@ SDL_bool Wayland_Vulkan_CreateSurface(SDL_VideoDevice *_this,
     return SDL_TRUE;
 }
 
+void Wayland_Vulkan_DestroySurface(SDL_VideoDevice *_this,
+                                   VkInstance instance,
+                                   VkSurfaceKHR surface,
+                                   const struct VkAllocationCallbacks *allocator)
+{
+    if (_this->vulkan_config.loader_handle) {
+        SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator);
+    }
+}
+
 #endif
diff --git a/src/video/wayland/SDL_waylandvulkan.h b/src/video/wayland/SDL_waylandvulkan.h
index e154d1a0ac82a..835442536e759 100644
--- a/src/video/wayland/SDL_waylandvulkan.h
+++ b/src/video/wayland/SDL_waylandvulkan.h
@@ -29,7 +29,7 @@
 #ifndef SDL_waylandvulkan_h_
 #define SDL_waylandvulkan_h_
 
-#include <SDL3/SDL_vulkan.h>
+#include "../SDL_vulkan_internal.h"
 #include "../SDL_sysvideo.h"
 
 #if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_WAYLAND)
@@ -37,12 +37,16 @@
 int Wayland_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path);
 void Wayland_Vulkan_UnloadLibrary(SDL_VideoDevice *_this);
 char const* const* Wayland_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this,
-                                              Uint32 *count);
+                                                        Uint32 *count);
 SDL_bool Wayland_Vulkan_CreateSurface(SDL_VideoDevice *_this,
                                       SDL_Window *window,
                                       VkInstance instance,
                                       const struct VkAllocationCallbacks *allocator,
                                       VkSurfaceKHR *surface);
+void Wayland_Vulkan_DestroySurface(SDL_VideoDevice *_this,
+                                   VkInstance instance,
+                                   VkSurfaceKHR surface,
+                                   const struct VkAllocationCallbacks *allocator);
 
 #endif
 
diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c
index 5dc929d872cd2..44cd3a55d0a7f 100644
--- a/src/video/windows/SDL_windowsvideo.c
+++ b/src/video/windows/SDL_windowsvideo.c
@@ -259,6 +259,7 @@ static SDL_VideoDevice *WIN_CreateDevice(void)
     device->Vulkan_UnloadLibrary = WIN_Vulkan_UnloadLibrary;
     device->Vulkan_GetInstanceExtensions = WIN_Vulkan_GetInstanceExtensions;
     device->Vulkan_CreateSurface = WIN_Vulkan_CreateSurface;
+    device->Vulkan_DestroySurface = WIN_Vulkan_DestroySurface;
 #endif
 
 #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
diff --git a/src/video/windows/SDL_windowsvulkan.c b/src/video/windows/SDL_windowsvulkan.c
index 0115904f47641..7aa28c935a5e5 100644
--- a/src/video/windows/SDL_windowsvulkan.c
+++ b/src/video/windows/SDL_windowsvulkan.c
@@ -160,4 +160,14 @@ SDL_bool WIN_Vulkan_CreateSurface(SDL_VideoDevice *_this,
     return SDL_TRUE;
 }
 
+void WIN_Vulkan_DestroySurface(SDL_VideoDevice *_this,
+                               VkInstance instance,
+                               VkSurfaceKHR surface,
+                               const struct VkAllocationCallbacks *allocator)
+{
+    if (_this->vulkan_config.loader_handle) {
+        SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator);
+    }
+}
+
 #endif
diff --git a/src/video/windows/SDL_windowsvulkan.h b/src/video/windows/SDL_windowsvulkan.h
index 088e204148e4d..342156c3a292e 100644
--- a/src/video/windows/SDL_windowsvulkan.h
+++ b/src/video/windows/SDL_windowsvulkan.h
@@ -29,20 +29,24 @@
 #ifndef SDL_windowsvulkan_h_
 #define SDL_windowsvulkan_h_
 
-#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_WINDOWS)
-
 #include "../SDL_vulkan_internal.h"
 #include "../SDL_sysvideo.h"
 
+#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_WINDOWS)
+
 int WIN_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path);
 void WIN_Vulkan_UnloadLibrary(SDL_VideoDevice *_this);
 char const* const* WIN_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this,
-                                          Uint32 *count);
+                                                    Uint32 *count);
 SDL_bool WIN_Vulkan_CreateSurface(SDL_VideoDevice *_this,
                                   SDL_Window *window,
                                   VkInstance instance,
                                   const struct VkAllocationCallbacks *allocator,
                                   VkSurfaceKHR *surface);
+void WIN_Vulkan_DestroySurface(SDL_VideoDevice *_this,
+                               VkInstance instance,
+                               VkSurfaceKHR surface,
+                               const struct VkAllocationCallbacks *allocator);
 
 #endif
 
diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c
index 7a50119d26306..64ffb763fc037 100644
--- a/src/video/x11/SDL_x11video.c
+++ b/src/video/x11/SDL_x11video.c
@@ -279,6 +279,7 @@ static SDL_VideoDevice *X11_CreateDevice(void)
     device->Vulkan_UnloadLibrary = X11_Vulkan_UnloadLibrary;
     device->Vulkan_GetInstanceExtensions = X11_Vulkan_GetInstanceExtensions;
     device->Vulkan_CreateSurface = X11_Vulkan_CreateSurface;
+    device->Vulkan_DestroySurface = X11_Vulkan_DestroySurface;
 #endif
 
 #ifdef SDL_USE_LIBDBUS
diff --git a/src/video/x11/SDL_x11vulkan.c b/src/video/x11/SDL_x11vulkan.c
index 88223ad650e3a..ef0d87259de48 100644
--- a/src/video/x11/SDL_x11vulkan.c
+++ b/src/video/x11/SDL_x11vulkan.c
@@ -230,4 +230,14 @@ SDL_bool X11_Vulkan_CreateSurface(SDL_VideoDevice *_this,
     }
 }
 
+void X11_Vulkan_DestroySurface(SDL_VideoDevice *_this,
+                               VkInstance instance,
+                               VkSurfaceKHR surface,
+                               const struct VkAllocationCallbacks *allocator)
+{
+    if (_this->vulkan_config.loader_handle) {
+        SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator);
+    }
+}
+
 #endif
diff --git a/src/video/x11/SDL_x11vulkan.h b/src/video/x11/SDL_x11vulkan.h
index 2c81a33a705c5..474ae15b7d2c6 100644
--- a/src/video/x11/SDL_x11vulkan.h
+++ b/src/video/x11/SDL_x11vulkan.h
@@ -23,7 +23,8 @@
 #ifndef SDL_x11vulkan_h_
 #define SDL_x11vulkan_h_
 
-#include <SDL3/SDL_vulkan.h>
+#include "../SDL_vulkan_internal.h"
+#include "../SDL_sysvideo.h"
 
 #if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_X11)
 
@@ -33,12 +34,16 @@ typedef xcb_connection_t *(*PFN_XGetXCBConnection)(Display *dpy);
 int X11_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path);
 void X11_Vulkan_UnloadLibrary(SDL_VideoDevice *_this);
 char const* const* X11_Vulkan_GetInstanceExtensions(SDL_VideoDevice

(Patch may be truncated, please check the link at the top of this post.)