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#include "../../SDL_internal.h"
27
28#if SDL_VIDEO_VULKAN && SDL_VIDEO_DRIVER_COCOA
29
30#include "SDL_cocoavideo.h"
31#include "SDL_cocoawindow.h"
32#include "SDL_assert.h"
33
34#include "SDL_loadso.h"
35#include "SDL_cocoametalview.h"
36#include "SDL_cocoavulkan.h"
37#include "SDL_syswm.h"
38
39#include <dlfcn.h>
40
41const char* defaultPaths[] = {
42    "vulkan.framework/vulkan",
43    "libvulkan.1.dylib",
44    "libvulkan.dylib",
45    "MoltenVK.framework/MoltenVK",
46    "libMoltenVK.dylib"
47};
48
49/* Since libSDL is most likely a .dylib, need RTLD_DEFAULT not RTLD_SELF. */
50#define DEFAULT_HANDLE RTLD_DEFAULT
51
52int Cocoa_Vulkan_LoadLibrary(_THIS, const char *path)
53{
54    VkExtensionProperties *extensions = NULL;
55    Uint32 extensionCount = 0;
56    SDL_bool hasSurfaceExtension = SDL_FALSE;
57    SDL_bool hasMacOSSurfaceExtension = SDL_FALSE;
58    PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL;
59
60    if (_this->vulkan_config.loader_handle) {
61        return SDL_SetError("Vulkan Portability library is already loaded.");
62    }
63
64    /* Load the Vulkan loader library */
65    if (!path) {
66        path = SDL_getenv("SDL_VULKAN_LIBRARY");
67    }
68
69    if (!path) {
70        /* Handle the case where Vulkan Portability is linked statically. */
71        vkGetInstanceProcAddr =
72         (PFN_vkGetInstanceProcAddr)dlsym(DEFAULT_HANDLE,
73                                          "vkGetInstanceProcAddr");
74    }
75
76    if (vkGetInstanceProcAddr) {
77        _this->vulkan_config.loader_handle = DEFAULT_HANDLE;
78    } else {
79        const char** paths;
80        const char *foundPath = NULL;
81        int numPaths;
82        int i;
83
84        if (path) {
85            paths = &path;
86            numPaths = 1;
87        } else {
88            /* Look for framework or .dylib packaged with the application
89             * instead. */
90            paths = defaultPaths;
91            numPaths = SDL_arraysize(defaultPaths);
92        }
93
94        for (i = 0; i < numPaths && _this->vulkan_config.loader_handle == NULL; i++) {
95            foundPath = paths[i];
96            _this->vulkan_config.loader_handle = SDL_LoadObject(foundPath);
97        }
98
99        if (_this->vulkan_config.loader_handle == NULL) {
100            return SDL_SetError("Failed to load Vulkan Portability library");
101        }
102
103        SDL_strlcpy(_this->vulkan_config.loader_path, foundPath,
104                    SDL_arraysize(_this->vulkan_config.loader_path));
105        vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_LoadFunction(
106            _this->vulkan_config.loader_handle, "vkGetInstanceProcAddr");
107    }
108
109    if (!vkGetInstanceProcAddr) {
110        SDL_SetError("Failed to find %s in either executable or %s: %s",
111                     "vkGetInstanceProcAddr",
112                     _this->vulkan_config.loader_path,
113                     (const char *) dlerror());
114        goto fail;
115    }
116
117    _this->vulkan_config.vkGetInstanceProcAddr = (void *)vkGetInstanceProcAddr;
118    _this->vulkan_config.vkEnumerateInstanceExtensionProperties =
119        (void *)((PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr)(
120            VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties");
121    if (!_this->vulkan_config.vkEnumerateInstanceExtensionProperties) {
122        goto fail;
123    }
124    extensions = SDL_Vulkan_CreateInstanceExtensionsList(
125        (PFN_vkEnumerateInstanceExtensionProperties)
126            _this->vulkan_config.vkEnumerateInstanceExtensionProperties,
127        &extensionCount);
128    if (!extensions) {
129        goto fail;
130    }
131    for (Uint32 i = 0; i < extensionCount; i++) {
132        if (SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
133            hasSurfaceExtension = SDL_TRUE;
134        } else if (SDL_strcmp(VK_MVK_MACOS_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
135            hasMacOSSurfaceExtension = SDL_TRUE;
136        }
137    }
138    SDL_free(extensions);
139    if (!hasSurfaceExtension) {
140        SDL_SetError("Installed Vulkan Portability library doesn't implement the "
141                     VK_KHR_SURFACE_EXTENSION_NAME " extension");
142        goto fail;
143    } else if (!hasMacOSSurfaceExtension) {
144        SDL_SetError("Installed Vulkan Portability library doesn't implement the "
145                     VK_MVK_MACOS_SURFACE_EXTENSION_NAME "extension");
146        goto fail;
147    }
148    return 0;
149
150fail:
151    SDL_UnloadObject(_this->vulkan_config.loader_handle);
152    _this->vulkan_config.loader_handle = NULL;
153    return -1;
154}
155
156void Cocoa_Vulkan_UnloadLibrary(_THIS)
157{
158    if (_this->vulkan_config.loader_handle) {
159        if (_this->vulkan_config.loader_handle != DEFAULT_HANDLE) {
160            SDL_UnloadObject(_this->vulkan_config.loader_handle);
161        }
162        _this->vulkan_config.loader_handle = NULL;
163    }
164}
165
166SDL_bool Cocoa_Vulkan_GetInstanceExtensions(_THIS,
167                                          SDL_Window *window,
168                                          unsigned *count,
169                                          const char **names)
170{
171    static const char *const extensionsForCocoa[] = {
172        VK_KHR_SURFACE_EXTENSION_NAME, VK_MVK_MACOS_SURFACE_EXTENSION_NAME
173    };
174    if (!_this->vulkan_config.loader_handle) {
175        SDL_SetError("Vulkan is not loaded");
176        return SDL_FALSE;
177    }
178    return SDL_Vulkan_GetInstanceExtensions_Helper(
179            count, names, SDL_arraysize(extensionsForCocoa),
180            extensionsForCocoa);
181}
182
183SDL_bool Cocoa_Vulkan_CreateSurface(_THIS,
184                                  SDL_Window *window,
185                                  VkInstance instance,
186                                  VkSurfaceKHR *surface)
187{
188    PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr =
189        (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr;
190    PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK =
191        (PFN_vkCreateMacOSSurfaceMVK)vkGetInstanceProcAddr(
192                                            (VkInstance)instance,
193                                            "vkCreateMacOSSurfaceMVK");
194    VkMacOSSurfaceCreateInfoMVK createInfo = {};
195    VkResult result;
196    SDL_MetalView metalview;
197
198    if (!_this->vulkan_config.loader_handle) {
199        SDL_SetError("Vulkan is not loaded");
200        return SDL_FALSE;
201    }
202
203    if (!vkCreateMacOSSurfaceMVK) {
204        SDL_SetError(VK_MVK_MACOS_SURFACE_EXTENSION_NAME
205                     " extension is not enabled in the Vulkan instance.");
206        return SDL_FALSE;
207    }
208
209    metalview = Cocoa_Metal_CreateView(_this, window);
210    if (metalview == NULL) {
211        return SDL_FALSE;
212    }
213
214    createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
215    createInfo.pNext = NULL;
216    createInfo.flags = 0;
217    createInfo.pView = (const void *)metalview;
218    result = vkCreateMacOSSurfaceMVK(instance, &createInfo,
219                                       NULL, surface);
220    if (result != VK_SUCCESS) {
221        Cocoa_Metal_DestroyView(_this, metalview);
222        SDL_SetError("vkCreateMacOSSurfaceMVK failed: %s",
223                     SDL_Vulkan_GetResultString(result));
224        return SDL_FALSE;
225    }
226
227    /* Unfortunately there's no SDL_Vulkan_DestroySurface function we can call
228     * Metal_DestroyView from. Right now the metal view's ref count is +2 (one
229     * from returning a new view object in CreateView, and one because it's
230     * a subview of the window.) If we release the view here to make it +1, it
231     * will be destroyed when the window is destroyed. */
232    CFBridgingRelease(metalview);
233
234    return SDL_TRUE;
235}
236
237void Cocoa_Vulkan_GetDrawableSize(_THIS, SDL_Window *window, int *w, int *h)
238{
239    Cocoa_Metal_GetDrawableSize(_this, window, w, h);
240}
241
242#endif
243
244/* vim: set ts=4 sw=4 expandtab: */
245