1/*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
4
5  This software is provided 'as-is', without any express or implied
6  warranty.  In no event will the authors be held liable for any damages
7  arising from the use of this software.
8
9  Permission is granted to anyone to use this software for any purpose,
10  including commercial applications, and to alter it and redistribute it
11  freely, subject to the following restrictions:
12
13  1. The origin of this software must not be misrepresented; you must not
14     claim that you wrote the original software. If you use this software
15     in a product, an acknowledgment in the product documentation would be
16     appreciated but is not required.
17  2. Altered source versions must be plainly marked as such, and must not be
18     misrepresented as being the original software.
19  3. This notice may not be removed or altered from any source distribution.
20*/
21
22/*
23 * @author Mark Callow, www.edgewise-consulting.com. Based on Jacob Lifshay's
24 * SDL_x11vulkan.c.
25 */
26
27#include "../../SDL_internal.h"
28
29#if SDL_VIDEO_VULKAN && SDL_VIDEO_DRIVER_UIKIT
30
31#include "SDL_uikitvideo.h"
32#include "SDL_uikitwindow.h"
33#include "SDL_assert.h"
34
35#include "SDL_loadso.h"
36#include "SDL_uikitvulkan.h"
37#include "SDL_uikitmetalview.h"
38#include "SDL_syswm.h"
39
40#include <dlfcn.h>
41
42const char* defaultPaths[] = {
43    "libvulkan.dylib",
44};
45
46/* Since libSDL is static, could use RTLD_SELF. Using RTLD_DEFAULT is future
47 * proofing. */
48#define DEFAULT_HANDLE RTLD_DEFAULT
49
50int UIKit_Vulkan_LoadLibrary(_THIS, const char *path)
51{
52    VkExtensionProperties *extensions = NULL;
53    Uint32 extensionCount = 0;
54    SDL_bool hasSurfaceExtension = SDL_FALSE;
55    SDL_bool hasIOSSurfaceExtension = SDL_FALSE;
56    PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL;
57
58    if (_this->vulkan_config.loader_handle) {
59        return SDL_SetError("Vulkan Portability library is already loaded.");
60    }
61
62    /* Load the Vulkan loader library */
63    if (!path) {
64        path = SDL_getenv("SDL_VULKAN_LIBRARY");
65    }
66
67    if (!path) {
68        /* Handle the case where Vulkan Portability is linked statically. */
69        vkGetInstanceProcAddr =
70        (PFN_vkGetInstanceProcAddr)dlsym(DEFAULT_HANDLE,
71                                         "vkGetInstanceProcAddr");
72    }
73
74    if (vkGetInstanceProcAddr) {
75        _this->vulkan_config.loader_handle = DEFAULT_HANDLE;
76    } else {
77        const char** paths;
78        const char *foundPath = NULL;
79        int numPaths;
80        int i;
81
82        if (path) {
83            paths = &path;
84            numPaths = 1;
85        } else {
86            /* Look for the .dylib packaged with the application instead. */
87            paths = defaultPaths;
88            numPaths = SDL_arraysize(defaultPaths);
89        }
90
91        for (i = 0; i < numPaths && _this->vulkan_config.loader_handle == NULL; i++) {
92            foundPath = paths[i];
93            _this->vulkan_config.loader_handle = SDL_LoadObject(foundPath);
94        }
95
96        if (_this->vulkan_config.loader_handle == NULL) {
97            return SDL_SetError("Failed to load Vulkan Portability library");
98        }
99
100        SDL_strlcpy(_this->vulkan_config.loader_path, path,
101                    SDL_arraysize(_this->vulkan_config.loader_path));
102        vkGetInstanceProcAddr =
103            (PFN_vkGetInstanceProcAddr)SDL_LoadFunction(
104                                    _this->vulkan_config.loader_handle,
105                                    "vkGetInstanceProcAddr");
106    }
107
108    if (!vkGetInstanceProcAddr) {
109        SDL_SetError("Failed to find %s in either executable or %s: %s",
110                     "vkGetInstanceProcAddr",
111                     "linked Vulkan Portability library",
112                     (const char *) dlerror());
113        goto fail;
114    }
115
116    _this->vulkan_config.vkGetInstanceProcAddr = (void *)vkGetInstanceProcAddr;
117    _this->vulkan_config.vkEnumerateInstanceExtensionProperties =
118        (void *)((PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr)(
119            VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties");
120
121    if (!_this->vulkan_config.vkEnumerateInstanceExtensionProperties) {
122        SDL_SetError("No vkEnumerateInstanceExtensionProperties found.");
123        goto fail;
124    }
125
126    extensions = SDL_Vulkan_CreateInstanceExtensionsList(
127        (PFN_vkEnumerateInstanceExtensionProperties)
128            _this->vulkan_config.vkEnumerateInstanceExtensionProperties,
129        &extensionCount);
130
131    if (!extensions) {
132        goto fail;
133    }
134
135    for (Uint32 i = 0; i < extensionCount; i++) {
136        if (SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
137            hasSurfaceExtension = SDL_TRUE;
138        } else if (SDL_strcmp(VK_MVK_IOS_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
139            hasIOSSurfaceExtension = SDL_TRUE;
140        }
141    }
142
143    SDL_free(extensions);
144
145    if (!hasSurfaceExtension) {
146        SDL_SetError("Installed Vulkan Portability doesn't implement the "
147                     VK_KHR_SURFACE_EXTENSION_NAME " extension");
148        goto fail;
149    } else if (!hasIOSSurfaceExtension) {
150        SDL_SetError("Installed Vulkan Portability doesn't implement the "
151                     VK_MVK_IOS_SURFACE_EXTENSION_NAME "extension");
152        goto fail;
153    }
154
155    return 0;
156
157fail:
158    _this->vulkan_config.loader_handle = NULL;
159    return -1;
160}
161
162void UIKit_Vulkan_UnloadLibrary(_THIS)
163{
164    if (_this->vulkan_config.loader_handle) {
165        if (_this->vulkan_config.loader_handle != DEFAULT_HANDLE) {
166            SDL_UnloadObject(_this->vulkan_config.loader_handle);
167        }
168        _this->vulkan_config.loader_handle = NULL;
169    }
170}
171
172SDL_bool UIKit_Vulkan_GetInstanceExtensions(_THIS,
173                                          SDL_Window *window,
174                                          unsigned *count,
175                                          const char **names)
176{
177    static const char *const extensionsForUIKit[] = {
178        VK_KHR_SURFACE_EXTENSION_NAME, VK_MVK_IOS_SURFACE_EXTENSION_NAME
179    };
180    if (!_this->vulkan_config.loader_handle) {
181        SDL_SetError("Vulkan is not loaded");
182        return SDL_FALSE;
183    }
184
185    return SDL_Vulkan_GetInstanceExtensions_Helper(
186            count, names, SDL_arraysize(extensionsForUIKit),
187            extensionsForUIKit);
188}
189
190SDL_bool UIKit_Vulkan_CreateSurface(_THIS,
191                                  SDL_Window *window,
192                                  VkInstance instance,
193                                  VkSurfaceKHR *surface)
194{
195    PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr =
196        (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr;
197    PFN_vkCreateIOSSurfaceMVK vkCreateIOSSurfaceMVK =
198        (PFN_vkCreateIOSSurfaceMVK)vkGetInstanceProcAddr(
199                                            (VkInstance)instance,
200                                            "vkCreateIOSSurfaceMVK");
201    VkIOSSurfaceCreateInfoMVK createInfo = {};
202    VkResult result;
203    SDL_MetalView metalview;
204
205    if (!_this->vulkan_config.loader_handle) {
206        SDL_SetError("Vulkan is not loaded");
207        return SDL_FALSE;
208    }
209
210    if (!vkCreateIOSSurfaceMVK) {
211        SDL_SetError(VK_MVK_IOS_SURFACE_EXTENSION_NAME
212                     " extension is not enabled in the Vulkan instance.");
213        return SDL_FALSE;
214    }
215
216    metalview = UIKit_Metal_CreateView(_this, window);
217    if (metalview == NULL) {
218        return SDL_FALSE;
219    }
220
221    createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
222    createInfo.pNext = NULL;
223    createInfo.flags = 0;
224    createInfo.pView = (const void *)metalview;
225    result = vkCreateIOSSurfaceMVK(instance, &createInfo,
226                                       NULL, surface);
227    if (result != VK_SUCCESS) {
228        UIKit_Metal_DestroyView(_this, metalview);
229        SDL_SetError("vkCreateIOSSurfaceMVK failed: %s",
230                     SDL_Vulkan_GetResultString(result));
231        return SDL_FALSE;
232    }
233
234    /* Unfortunately there's no SDL_Vulkan_DestroySurface function we can call
235     * Metal_DestroyView from. Right now the metal view's ref count is +2 (one
236     * from returning a new view object in CreateView, and one because it's
237     * a subview of the window.) If we release the view here to make it +1, it
238     * will be destroyed when the window is destroyed. */
239    CFBridgingRelease(metalview);
240
241    return SDL_TRUE;
242}
243
244void UIKit_Vulkan_GetDrawableSize(_THIS, SDL_Window *window, int *w, int *h)
245{
246    UIKit_Metal_GetDrawableSize(_this, window, w, h);
247}
248
249#endif
250
251/* vi: set ts=4 sw=4 expandtab: */
252