SDL: cocoa/uikit: Support VK_EXT_metal_surface

From 029a9b2fa11157a7beca5635d3b7f743cf417138 Mon Sep 17 00:00:00 2001
From: Luke Street <[EMAIL REDACTED]>
Date: Thu, 19 May 2022 12:57:35 -0700
Subject: [PATCH] cocoa/uikit: Support VK_EXT_metal_surface

Uses VK_EXT_metal_surface (vkCreateMetalSurfaceEXT)
when possible, otherwise falls back to the obsoleted
VK_MVK_macos_surface and VK_MVK_ios_surface.

Fixes #3906
---
 src/video/SDL_vulkan_internal.h   |  2 ++
 src/video/cocoa/SDL_cocoavulkan.m | 59 ++++++++++++++++++++++---------
 src/video/uikit/SDL_uikitvulkan.m | 59 ++++++++++++++++++++++---------
 3 files changed, 86 insertions(+), 34 deletions(-)

diff --git a/src/video/SDL_vulkan_internal.h b/src/video/SDL_vulkan_internal.h
index 1ec1ab47330..ae9b962209f 100644
--- a/src/video/SDL_vulkan_internal.h
+++ b/src/video/SDL_vulkan_internal.h
@@ -34,12 +34,14 @@
 #define VK_USE_PLATFORM_ANDROID_KHR
 #endif
 #if SDL_VIDEO_DRIVER_COCOA
+#define VK_USE_PLATFORM_METAL_EXT
 #define VK_USE_PLATFORM_MACOS_MVK
 #endif
 #if SDL_VIDEO_DRIVER_DIRECTFB
 #define VK_USE_PLATFORM_DIRECTFB_EXT
 #endif
 #if SDL_VIDEO_DRIVER_UIKIT
+#define VK_USE_PLATFORM_METAL_EXT
 #define VK_USE_PLATFORM_IOS_MVK
 #endif
 #if SDL_VIDEO_DRIVER_WAYLAND
diff --git a/src/video/cocoa/SDL_cocoavulkan.m b/src/video/cocoa/SDL_cocoavulkan.m
index 20c83d47d78..f91924cd0ca 100644
--- a/src/video/cocoa/SDL_cocoavulkan.m
+++ b/src/video/cocoa/SDL_cocoavulkan.m
@@ -53,6 +53,7 @@ int Cocoa_Vulkan_LoadLibrary(_THIS, const char *path)
     VkExtensionProperties *extensions = NULL;
     Uint32 extensionCount = 0;
     SDL_bool hasSurfaceExtension = SDL_FALSE;
+    SDL_bool hasMetalSurfaceExtension = SDL_FALSE;
     SDL_bool hasMacOSSurfaceExtension = SDL_FALSE;
     PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL;
 
@@ -130,6 +131,8 @@ int Cocoa_Vulkan_LoadLibrary(_THIS, const char *path)
     for (Uint32 i = 0; i < extensionCount; i++) {
         if (SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
             hasSurfaceExtension = SDL_TRUE;
+        } else if (SDL_strcmp(VK_EXT_METAL_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
+            hasMetalSurfaceExtension = SDL_TRUE;
         } else if (SDL_strcmp(VK_MVK_MACOS_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
             hasMacOSSurfaceExtension = SDL_TRUE;
         }
@@ -139,9 +142,10 @@ int Cocoa_Vulkan_LoadLibrary(_THIS, const char *path)
         SDL_SetError("Installed Vulkan Portability library doesn't implement the "
                      VK_KHR_SURFACE_EXTENSION_NAME " extension");
         goto fail;
-    } else if (!hasMacOSSurfaceExtension) {
+    } else if (!hasMetalSurfaceExtension && !hasMacOSSurfaceExtension) {
         SDL_SetError("Installed Vulkan Portability library doesn't implement the "
-                     VK_MVK_MACOS_SURFACE_EXTENSION_NAME "extension");
+                     VK_EXT_METAL_SURFACE_EXTENSION_NAME " or "
+                     VK_MVK_MACOS_SURFACE_EXTENSION_NAME " extensions");
         goto fail;
     }
     return 0;
@@ -186,11 +190,14 @@ SDL_bool Cocoa_Vulkan_CreateSurface(_THIS,
 {
     PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr =
         (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr;
+    PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT =
+        (PFN_vkCreateMetalSurfaceEXT)vkGetInstanceProcAddr(
+                                            (VkInstance)instance,
+                                            "vkCreateMetalSurfaceEXT");
     PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK =
         (PFN_vkCreateMacOSSurfaceMVK)vkGetInstanceProcAddr(
                                             (VkInstance)instance,
                                             "vkCreateMacOSSurfaceMVK");
-    VkMacOSSurfaceCreateInfoMVK createInfo = {};
     VkResult result;
     SDL_MetalView metalview;
 
@@ -199,9 +206,10 @@ SDL_bool Cocoa_Vulkan_CreateSurface(_THIS,
         return SDL_FALSE;
     }
 
-    if (!vkCreateMacOSSurfaceMVK) {
-        SDL_SetError(VK_MVK_MACOS_SURFACE_EXTENSION_NAME
-                     " extension is not enabled in the Vulkan instance.");
+    if (!vkCreateMetalSurfaceEXT && !vkCreateMacOSSurfaceMVK) {
+        SDL_SetError(VK_EXT_METAL_SURFACE_EXTENSION_NAME " or "
+                     VK_MVK_MACOS_SURFACE_EXTENSION_NAME
+                     " extensions are not enabled in the Vulkan instance.");
         return SDL_FALSE;
     }
 
@@ -210,17 +218,34 @@ SDL_bool Cocoa_Vulkan_CreateSurface(_THIS,
         return SDL_FALSE;
     }
 
-    createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
-    createInfo.pNext = NULL;
-    createInfo.flags = 0;
-    createInfo.pView = (const void *)metalview;
-    result = vkCreateMacOSSurfaceMVK(instance, &createInfo,
-                                       NULL, surface);
-    if (result != VK_SUCCESS) {
-        Cocoa_Metal_DestroyView(_this, metalview);
-        SDL_SetError("vkCreateMacOSSurfaceMVK failed: %s",
-                     SDL_Vulkan_GetResultString(result));
-        return SDL_FALSE;
+    if (vkCreateMetalSurfaceEXT) {
+        VkMetalSurfaceCreateInfoEXT createInfo = {};
+        createInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
+        createInfo.pNext = NULL;
+        createInfo.flags = 0;
+        createInfo.pLayer = (__bridge const CAMetalLayer *)
+                            Cocoa_Metal_GetLayer(_this, metalview);
+        result = vkCreateMetalSurfaceEXT(instance, &createInfo, NULL, surface);
+        if (result != VK_SUCCESS) {
+            Cocoa_Metal_DestroyView(_this, metalview);
+            SDL_SetError("vkCreateMetalSurfaceEXT failed: %s",
+                         SDL_Vulkan_GetResultString(result));
+            return SDL_FALSE;
+        }
+    } else {
+        VkMacOSSurfaceCreateInfoMVK createInfo = {};
+        createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
+        createInfo.pNext = NULL;
+        createInfo.flags = 0;
+        createInfo.pView = (const void *)metalview;
+        result = vkCreateMacOSSurfaceMVK(instance, &createInfo,
+                                           NULL, surface);
+        if (result != VK_SUCCESS) {
+            Cocoa_Metal_DestroyView(_this, metalview);
+            SDL_SetError("vkCreateMacOSSurfaceMVK failed: %s",
+                         SDL_Vulkan_GetResultString(result));
+            return SDL_FALSE;
+        }
     }
 
     /* Unfortunately there's no SDL_Vulkan_DestroySurface function we can call
diff --git a/src/video/uikit/SDL_uikitvulkan.m b/src/video/uikit/SDL_uikitvulkan.m
index f0f2d2b04a3..891f5bb09a4 100644
--- a/src/video/uikit/SDL_uikitvulkan.m
+++ b/src/video/uikit/SDL_uikitvulkan.m
@@ -51,6 +51,7 @@ int UIKit_Vulkan_LoadLibrary(_THIS, const char *path)
     VkExtensionProperties *extensions = NULL;
     Uint32 extensionCount = 0;
     SDL_bool hasSurfaceExtension = SDL_FALSE;
+    SDL_bool hasMetalSurfaceExtension = SDL_FALSE;
     SDL_bool hasIOSSurfaceExtension = SDL_FALSE;
     PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL;
 
@@ -134,6 +135,8 @@ int UIKit_Vulkan_LoadLibrary(_THIS, const char *path)
     for (Uint32 i = 0; i < extensionCount; i++) {
         if (SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
             hasSurfaceExtension = SDL_TRUE;
+        } else if (SDL_strcmp(VK_EXT_METAL_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
+            hasMetalSurfaceExtension = SDL_TRUE;
         } else if (SDL_strcmp(VK_MVK_IOS_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
             hasIOSSurfaceExtension = SDL_TRUE;
         }
@@ -145,9 +148,10 @@ int UIKit_Vulkan_LoadLibrary(_THIS, const char *path)
         SDL_SetError("Installed Vulkan Portability doesn't implement the "
                      VK_KHR_SURFACE_EXTENSION_NAME " extension");
         goto fail;
-    } else if (!hasIOSSurfaceExtension) {
+    } else if (!hasMetalSurfaceExtension && !hasIOSSurfaceExtension) {
         SDL_SetError("Installed Vulkan Portability doesn't implement the "
-                     VK_MVK_IOS_SURFACE_EXTENSION_NAME "extension");
+                     VK_EXT_METAL_SURFACE_EXTENSION_NAME " or "
+                     VK_MVK_IOS_SURFACE_EXTENSION_NAME " extensions");
         goto fail;
     }
 
@@ -193,11 +197,14 @@ SDL_bool UIKit_Vulkan_CreateSurface(_THIS,
 {
     PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr =
         (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr;
+    PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT =
+        (PFN_vkCreateMetalSurfaceEXT)vkGetInstanceProcAddr(
+                                            (VkInstance)instance,
+                                            "vkCreateMetalSurfaceEXT");
     PFN_vkCreateIOSSurfaceMVK vkCreateIOSSurfaceMVK =
         (PFN_vkCreateIOSSurfaceMVK)vkGetInstanceProcAddr(
                                             (VkInstance)instance,
                                             "vkCreateIOSSurfaceMVK");
-    VkIOSSurfaceCreateInfoMVK createInfo = {};
     VkResult result;
     SDL_MetalView metalview;
 
@@ -206,9 +213,10 @@ SDL_bool UIKit_Vulkan_CreateSurface(_THIS,
         return SDL_FALSE;
     }
 
-    if (!vkCreateIOSSurfaceMVK) {
-        SDL_SetError(VK_MVK_IOS_SURFACE_EXTENSION_NAME
-                     " extension is not enabled in the Vulkan instance.");
+    if (!vkCreateMetalSurfaceEXT && !vkCreateIOSSurfaceMVK) {
+        SDL_SetError(VK_EXT_METAL_SURFACE_EXTENSION_NAME " or "
+                     VK_MVK_IOS_SURFACE_EXTENSION_NAME
+                     " extensions are not enabled in the Vulkan instance.");
         return SDL_FALSE;
     }
 
@@ -217,17 +225,34 @@ SDL_bool UIKit_Vulkan_CreateSurface(_THIS,
         return SDL_FALSE;
     }
 
-    createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
-    createInfo.pNext = NULL;
-    createInfo.flags = 0;
-    createInfo.pView = (const void *)metalview;
-    result = vkCreateIOSSurfaceMVK(instance, &createInfo,
-                                       NULL, surface);
-    if (result != VK_SUCCESS) {
-        UIKit_Metal_DestroyView(_this, metalview);
-        SDL_SetError("vkCreateIOSSurfaceMVK failed: %s",
-                     SDL_Vulkan_GetResultString(result));
-        return SDL_FALSE;
+    if (vkCreateMetalSurfaceEXT) {
+        VkMetalSurfaceCreateInfoEXT createInfo = {};
+        createInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
+        createInfo.pNext = NULL;
+        createInfo.flags = 0;
+        createInfo.pLayer = (__bridge const CAMetalLayer *)
+                            UIKit_Metal_GetLayer(_this, metalview);
+        result = vkCreateMetalSurfaceEXT(instance, &createInfo, NULL, surface);
+        if (result != VK_SUCCESS) {
+            UIKit_Metal_DestroyView(_this, metalview);
+            SDL_SetError("vkCreateMetalSurfaceEXT failed: %s",
+                         SDL_Vulkan_GetResultString(result));
+            return SDL_FALSE;
+        }
+    } else {
+        VkIOSSurfaceCreateInfoMVK createInfo = {};
+        createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
+        createInfo.pNext = NULL;
+        createInfo.flags = 0;
+        createInfo.pView = (const void *)metalview;
+        result = vkCreateIOSSurfaceMVK(instance, &createInfo,
+                                           NULL, surface);
+        if (result != VK_SUCCESS) {
+            UIKit_Metal_DestroyView(_this, metalview);
+            SDL_SetError("vkCreateIOSSurfaceMVK failed: %s",
+                         SDL_Vulkan_GetResultString(result));
+            return SDL_FALSE;
+        }
     }
 
     /* Unfortunately there's no SDL_Vulkan_DestroySurface function we can call