Cocoa: Add basic support for Vulkan via MoltenVK

This adds basic support for MoltenVK, a Vulkan implementation on top of
Metal, on macOS 10.11 and later.  It looks for MoltenVK in the process
via RTLD_DEFAULT symbol lookup if _GLFW_VULKAN_STATIC is disabled.

glfwCreateWindowSurface now creates and sets a CAMetalLayer for the
window content view, which is required for MoltenVK to function.

You must help CMake find MoltenVK for the Vulkan test to be built.

Fixes #870.
This commit is contained in:
Camilla Berglund 2016-10-14 01:46:56 +02:00
parent c3db1cae3f
commit e94d16667b
12 changed files with 182 additions and 22 deletions

View File

@ -20,6 +20,13 @@ if (WIN32)
"$ENV{VULKAN_SDK}/Bin32" "$ENV{VULKAN_SDK}/Bin32"
"$ENV{VK_SDK_PATH}/Bin32") "$ENV{VK_SDK_PATH}/Bin32")
endif() endif()
elseif (APPLE)
find_library(VULKAN_LIBRARY MoltenVK)
if (VULKAN_LIBRARY)
set(VULKAN_STATIC_LIBRARY ${VULKAN_LIBRARY})
find_path(VULKAN_INCLUDE_DIR NAMES vulkan/vulkan.h HINTS
"${VULKAN_LIBRARY}/Headers")
endif()
else() else()
find_path(VULKAN_INCLUDE_DIR NAMES vulkan/vulkan.h HINTS find_path(VULKAN_INCLUDE_DIR NAMES vulkan/vulkan.h HINTS
"$ENV{VULKAN_SDK}/include") "$ENV{VULKAN_SDK}/include")

View File

@ -129,6 +129,15 @@ if (MINGW)
endif() endif()
endif() endif()
if (APPLE)
# Dependencies required by the MoltenVK static library
set(GLFW_VULKAN_DEPS
"-lc++"
"-framework Cocoa"
"-framework Metal"
"-framework QuartzCore")
endif()
#-------------------------------------------------------------------- #--------------------------------------------------------------------
# Detect and select backend APIs # Detect and select backend APIs
#-------------------------------------------------------------------- #--------------------------------------------------------------------
@ -158,7 +167,10 @@ endif()
#-------------------------------------------------------------------- #--------------------------------------------------------------------
if (GLFW_VULKAN_STATIC) if (GLFW_VULKAN_STATIC)
if (VULKAN_FOUND AND VULKAN_STATIC_LIBRARY) if (VULKAN_FOUND AND VULKAN_STATIC_LIBRARY)
list(APPEND glfw_LIBRARIES ${VULKAN_STATIC_LIBRARY}) list(APPEND glfw_LIBRARIES ${VULKAN_STATIC_LIBRARY} ${GLFW_VULKAN_DEPS})
if (BUILD_SHARED_LIBS)
message(WARNING "Linking Vulkan loader static library into GLFW")
endif()
else() else()
if (BUILD_SHARED_LIBS OR GLFW_BUILD_EXAMPLES OR GLFW_BUILD_TESTS) if (BUILD_SHARED_LIBS OR GLFW_BUILD_EXAMPLES OR GLFW_BUILD_TESTS)
message(FATAL_ERROR "Vulkan loader static library not found") message(FATAL_ERROR "Vulkan loader static library not found")

View File

@ -110,6 +110,7 @@ information on what to include when reporting a bug.
- Bugfix: `glfwGetInstanceProcAddress` returned `NULL` for - Bugfix: `glfwGetInstanceProcAddress` returned `NULL` for
`vkGetInstanceProcAddr` when `_GLFW_VULKAN_STATIC` was enabled `vkGetInstanceProcAddr` when `_GLFW_VULKAN_STATIC` was enabled
- [Win32] Bugfix: Undecorated windows could not be iconified by the user (#861) - [Win32] Bugfix: Undecorated windows could not be iconified by the user (#861)
- [Cocoa] Added support for Vulkan window surface creation via MoltenVK (#870)
- [Cocoa] Bugfix: Disabling window aspect ratio would assert (#852) - [Cocoa] Bugfix: Disabling window aspect ratio would assert (#852)
- [Cocoa] Bugfix: Window creation failed to set first responder (#876,#883) - [Cocoa] Bugfix: Window creation failed to set first responder (#876,#883)
- [EGL] Added support for `EGL_KHR_get_all_proc_addresses` (#871) - [EGL] Added support for `EGL_KHR_get_all_proc_addresses` (#871)

View File

@ -185,10 +185,11 @@ a non-default value will cause @ref glfwCreateWindow to fail and the
@section compat_vulkan Vulkan loader and API @section compat_vulkan Vulkan loader and API
GLFW uses the standard system-wide Vulkan loader to access the Vulkan API. By default, GLFW uses the standard system-wide Vulkan loader to access the
This should be installed by graphics drivers and Vulkan SDKs. If this is not Vulkan API on all platforms except macOS. This is installed by both graphics
available, @ref glfwVulkanSupported will return `GLFW_FALSE` and all other drivers and Vulkan SDKs. If the loader is not found, @ref glfwVulkanSupported
Vulkan-related functions will fail with an @ref GLFW_API_UNAVAILABLE error. will return `GLFW_FALSE` and all other Vulkan-related functions will fail with
an @ref GLFW_API_UNAVAILABLE error.
@section compat_wsi Vulkan WSI extensions @section compat_wsi Vulkan WSI extensions
@ -201,6 +202,11 @@ surfaces on Microsoft Windows. If any of these extensions are not available,
@ref glfwGetRequiredInstanceExtensions will return an empty list and window @ref glfwGetRequiredInstanceExtensions will return an empty list and window
surface creation will fail. surface creation will fail.
GLFW uses the `VK_KHR_surface` and `VK_MVK_macos_surface` extensions to create
surfaces on macOS. If any of these extensions are not available, @ref
glfwGetRequiredInstanceExtensions will return an empty list and window surface
creation will fail.
GLFW uses the `VK_KHR_surface` and either the `VK_KHR_xlib_surface` or GLFW uses the `VK_KHR_surface` and either the `VK_KHR_xlib_surface` or
`VK_KHR_xcb_surface` extensions to create surfaces on X11. If `VK_KHR_surface` `VK_KHR_xcb_surface` extensions to create surfaces on X11. If `VK_KHR_surface`
or both `VK_KHR_xlib_surface` and `VK_KHR_xcb_surface` are not available, @ref or both `VK_KHR_xlib_surface` and `VK_KHR_xcb_surface` are not available, @ref
@ -217,8 +223,4 @@ surfaces on Mir. If any of these extensions are not available, @ref
glfwGetRequiredInstanceExtensions will return an empty list and window surface glfwGetRequiredInstanceExtensions will return an empty list and window surface
creation will fail. creation will fail.
GLFW does not support any extensions for window surface creation on macOS,
meaning@ref glfwGetRequiredInstanceExtensions will return an empty list and
window surface creation will fail.
*/ */

View File

@ -16,6 +16,12 @@ GLFW now supports querying the platform dependent scancode of any key with
@ref glfwGetKeyScancode. @ref glfwGetKeyScancode.
@subsection news_33_moltenvk Support for Vulkan on macOS via MoltenVK
GLFW now supports the `VK_MVK_macos_surface` window surface creation extension
provided by MoltenVK.
@section news_32 New features in 3.2 @section news_32 New features in 3.2

View File

@ -11,8 +11,10 @@ with Vulkan concepts like loaders, devices, queues and surfaces and leaves it to
the Vulkan documentation to explain the details of Vulkan functions. the Vulkan documentation to explain the details of Vulkan functions.
To develop for Vulkan you should install an SDK for your platform, for example To develop for Vulkan you should install an SDK for your platform, for example
the [LunarG Vulkan SDK](https://vulkan.lunarg.com/). Apart from the headers and the [LunarG Vulkan SDK](https://vulkan.lunarg.com/) for Windows and Linux or
libraries, it also provides the validation layers necessary for development. [MoltenVK](https://moltengl.com/moltenvk/) for macOS. Apart from headers and
link libraries, they should also provide the validation layers necessary for
development.
The GLFW library does not need the Vulkan SDK to enable support for Vulkan. The GLFW library does not need the Vulkan SDK to enable support for Vulkan.
However, any Vulkan-specific test and example programs are built only if the However, any Vulkan-specific test and example programs are built only if the
@ -28,6 +30,26 @@ are also guides for the other areas of the GLFW API.
- @ref input_guide - @ref input_guide
@section vulkan_loader Linking against the Vulkan loader
By default, GLFW will look for the Vulkan loader on demand at runtime via its
standard name (`vulkan-1.dll` on Windows, `libvulkan.so.1` on Linux and other
Unix-like systems). This means that GLFW does not need to be linked against the
loader. However, it also means that if you are using the static library form of
the Vulkan loader GLFW will either fail to find it or (worse) use the wrong one.
The [GLFW_VULKAN_STATIC](@ref compile_options_shared) CMake option makes GLFW
link directly against the static form. Not linking against the Vulkan loader
will then be a compile-time error.
@macos MoltenVK only provides the static library form of the Vulkan loader, but
GLFW is able to find it without
[GLFW_VULKAN_STATIC](@ref compile_options_shared) as long as it is linked into
any of the binaries already loaded into the process. As it is a static library,
you must also link against its dependencies: the `Cocoa`, `Metal` and
`QuartzCore` frameworks and the `libc++` library.
@section vulkan_include Including the Vulkan and GLFW header files @section vulkan_include Including the Vulkan and GLFW header files
To include the Vulkan header, define [GLFW_INCLUDE_VULKAN](@ref build_macros) To include the Vulkan header, define [GLFW_INCLUDE_VULKAN](@ref build_macros)

View File

@ -176,7 +176,11 @@ extern "C" {
#endif #endif
#endif #endif
#if defined(GLFW_INCLUDE_VULKAN) #if defined(GLFW_INCLUDE_VULKAN)
#include <vulkan/vulkan.h> #if defined(__APPLE__)
#include <MoltenVK/vulkan/vulkan.h>
#else
#include <vulkan/vulkan.h>
#endif
#endif #endif
#if defined(GLFW_DLL) && defined(_GLFW_BUILD_DLL) #if defined(GLFW_DLL) && defined(_GLFW_BUILD_DLL)
@ -4225,6 +4229,9 @@ GLFWAPI int glfwVulkanSupported(void);
* returned array, as it is an error to specify an extension more than once in * returned array, as it is an error to specify an extension more than once in
* the `VkInstanceCreateInfo` struct. * the `VkInstanceCreateInfo` struct.
* *
* @remark @macos This function currently only supports the
* `VK_MVK_macos_surface` extension from MoltenVK.
*
* @pointer_lifetime The returned array is allocated and freed by GLFW. You * @pointer_lifetime The returned array is allocated and freed by GLFW. You
* should not free it yourself. It is guaranteed to be valid only until the * should not free it yourself. It is guaranteed to be valid only until the
* library is terminated. * library is terminated.
@ -4305,6 +4312,10 @@ GLFWAPI GLFWvkproc glfwGetInstanceProcAddress(VkInstance instance, const char* p
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
* GLFW_API_UNAVAILABLE and @ref GLFW_PLATFORM_ERROR. * GLFW_API_UNAVAILABLE and @ref GLFW_PLATFORM_ERROR.
* *
* @remark @macos This function currently always returns `GLFW_TRUE`, as the
* `VK_MVK_macos_surface` extension does not provide
* a `vkGetPhysicalDevice*PresentationSupport` type function.
*
* @thread_safety This function may be called from any thread. For * @thread_safety This function may be called from any thread. For
* synchronization details of Vulkan objects, see the Vulkan specification. * synchronization details of Vulkan objects, see the Vulkan specification.
* *
@ -4354,6 +4365,12 @@ GLFWAPI int glfwGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhys
* @ref glfwVulkanSupported and @ref glfwGetRequiredInstanceExtensions should * @ref glfwVulkanSupported and @ref glfwGetRequiredInstanceExtensions should
* eliminate almost all occurrences of these errors. * eliminate almost all occurrences of these errors.
* *
* @remark @macos This function currently only supports the
* `VK_MVK_macos_surface` extension from MoltenVK.
*
* @remark @macos This function creates and sets a `CAMetalLayer` instance for
* the window content view, which is required for MoltenVK to function.
*
* @thread_safety This function may be called from any thread. For * @thread_safety This function may be called from any thread. For
* synchronization details of Vulkan objects, see the Vulkan specification. * synchronization details of Vulkan objects, see the Vulkan specification.
* *

View File

@ -39,6 +39,18 @@
typedef void* id; typedef void* id;
#endif #endif
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
typedef struct VkMacOSSurfaceCreateInfoMVK
{
VkStructureType sType;
const void* pNext;
VkMacOSSurfaceCreateFlagsMVK flags;
const void* pView;
} VkMacOSSurfaceCreateInfoMVK;
typedef VkResult (APIENTRY *PFN_vkCreateMacOSSurfaceMVK)(VkInstance,const VkMacOSSurfaceCreateInfoMVK*,const VkAllocationCallbacks*,VkSurfaceKHR*);
#include "posix_tls.h" #include "posix_tls.h"
#include "cocoa_joystick.h" #include "cocoa_joystick.h"
#include "nsgl_context.h" #include "nsgl_context.h"
@ -74,6 +86,7 @@ typedef struct _GLFWwindowNS
id object; id object;
id delegate; id delegate;
id view; id view;
id layer;
GLFWbool maximized; GLFWbool maximized;

View File

@ -431,6 +431,16 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
return YES; return YES;
} }
- (BOOL)wantsUpdateLayer
{
return YES;
}
- (id)makeBackingLayer
{
return window->ns.layer;
}
- (void)cursorUpdate:(NSEvent *)event - (void)cursorUpdate:(NSEvent *)event
{ {
updateCursorImage(window); updateCursorImage(window);
@ -1653,13 +1663,18 @@ const char* _glfwPlatformGetClipboardString(_GLFWwindow* window)
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
{ {
if (!_glfw.vk.KHR_surface || !_glfw.vk.MVK_macos_surface)
return;
extensions[0] = "VK_KHR_surface";
extensions[1] = "VK_MVK_macos_surface";
} }
int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance,
VkPhysicalDevice device, VkPhysicalDevice device,
uint32_t queuefamily) uint32_t queuefamily)
{ {
return GLFW_FALSE; return GLFW_TRUE;
} }
VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
@ -1667,7 +1682,57 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
const VkAllocationCallbacks* allocator, const VkAllocationCallbacks* allocator,
VkSurfaceKHR* surface) VkSurfaceKHR* surface)
{ {
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101100
VkResult err;
VkMacOSSurfaceCreateInfoMVK sci;
PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK;
vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK)
vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK");
if (!vkCreateMacOSSurfaceMVK)
{
_glfwInputError(GLFW_API_UNAVAILABLE,
"Cocoa: Vulkan instance missing VK_MVK_macos_surface extension");
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
// HACK: Dynamically load Core Animation to avoid adding an extra
// dependency for the majority who don't use MoltenVK
NSBundle* bundle = [NSBundle bundleWithPath:@"/System/Library/Frameworks/QuartzCore.framework"];
if (!bundle)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Failed to find QuartzCore.framework");
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
// NOTE: Create the layer here as makeBackingLayer should not return nil
window->ns.layer = [[bundle classNamed:@"CAMetalLayer"] layer];
if (!window->ns.layer)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Failed to create layer for view");
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
[window->ns.view setWantsLayer:YES];
memset(&sci, 0, sizeof(sci));
sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
sci.pView = window->ns.view;
err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface);
if (err)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Failed to create Vulkan surface: %s",
_glfwGetVulkanResultString(err));
}
return err;
#else
return VK_ERROR_EXTENSION_NOT_PRESENT; return VK_ERROR_EXTENSION_NOT_PRESENT;
#endif
} }

View File

@ -110,6 +110,7 @@ typedef enum VkStructureType
VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000, VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000,
VK_STRUCTURE_TYPE_MIR_SURFACE_CREATE_INFO_KHR = 1000007000, VK_STRUCTURE_TYPE_MIR_SURFACE_CREATE_INFO_KHR = 1000007000,
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000, VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000,
VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000053000,
VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
} VkStructureType; } VkStructureType;
@ -458,6 +459,8 @@ struct _GLFWlibrary
GLFWbool KHR_surface; GLFWbool KHR_surface;
#if defined(_GLFW_WIN32) #if defined(_GLFW_WIN32)
GLFWbool KHR_win32_surface; GLFWbool KHR_win32_surface;
#elif defined(_GLFW_COCOA)
GLFWbool MVK_macos_surface;
#elif defined(_GLFW_X11) #elif defined(_GLFW_X11)
GLFWbool KHR_xlib_surface; GLFWbool KHR_xlib_surface;
GLFWbool KHR_xcb_surface; GLFWbool KHR_xcb_surface;

View File

@ -45,17 +45,18 @@ GLFWbool _glfwInitVulkan(int mode)
VkExtensionProperties* ep; VkExtensionProperties* ep;
uint32_t i, count; uint32_t i, count;
#if !defined(_GLFW_VULKAN_STATIC)
#if defined(_GLFW_WIN32)
const char* name = "vulkan-1.dll";
#else
const char* name = "libvulkan.so.1";
#endif
if (_glfw.vk.available) if (_glfw.vk.available)
return GLFW_TRUE; return GLFW_TRUE;
_glfw.vk.handle = _glfw_dlopen(name); #if !defined(_GLFW_VULKAN_STATIC)
#if defined(_GLFW_WIN32)
_glfw.vk.handle = _glfw_dlopen("vulkan-1.dll");
#elif defined(_GLFW_COCOA)
// NULL maps to RTLD_DEFAULT, which searches all loaded binaries
_glfw.vk.handle = _glfw_dlopen(NULL);
#else
_glfw.vk.handle = _glfw_dlopen("libvulkan.so.1");
#endif
if (!_glfw.vk.handle) if (!_glfw.vk.handle)
{ {
if (mode == _GLFW_REQUIRE_LOADER) if (mode == _GLFW_REQUIRE_LOADER)
@ -68,8 +69,16 @@ GLFWbool _glfwInitVulkan(int mode)
_glfw_dlsym(_glfw.vk.handle, "vkGetInstanceProcAddr"); _glfw_dlsym(_glfw.vk.handle, "vkGetInstanceProcAddr");
if (!_glfw.vk.GetInstanceProcAddr) if (!_glfw.vk.GetInstanceProcAddr)
{ {
#if defined(_GLFW_COCOA)
if (mode == _GLFW_REQUIRE_LOADER)
{
_glfwInputError(GLFW_API_UNAVAILABLE,
"Vulkan: vkGetInstanceProcAddr not found in process");
}
#else
_glfwInputError(GLFW_API_UNAVAILABLE, _glfwInputError(GLFW_API_UNAVAILABLE,
"Vulkan: Loader does not export vkGetInstanceProcAddr"); "Vulkan: Loader does not export vkGetInstanceProcAddr");
#endif
_glfwTerminateVulkan(); _glfwTerminateVulkan();
return GLFW_FALSE; return GLFW_FALSE;
@ -119,6 +128,9 @@ GLFWbool _glfwInitVulkan(int mode)
#if defined(_GLFW_WIN32) #if defined(_GLFW_WIN32)
else if (strcmp(ep[i].extensionName, "VK_KHR_win32_surface") == 0) else if (strcmp(ep[i].extensionName, "VK_KHR_win32_surface") == 0)
_glfw.vk.KHR_win32_surface = GLFW_TRUE; _glfw.vk.KHR_win32_surface = GLFW_TRUE;
#elif defined(_GLFW_COCOA)
else if (strcmp(ep[i].extensionName, "VK_MVK_macos_surface") == 0)
_glfw.vk.MVK_macos_surface = GLFW_TRUE;
#elif defined(_GLFW_X11) #elif defined(_GLFW_X11)
else if (strcmp(ep[i].extensionName, "VK_KHR_xlib_surface") == 0) else if (strcmp(ep[i].extensionName, "VK_KHR_xlib_surface") == 0)
_glfw.vk.KHR_xlib_surface = GLFW_TRUE; _glfw.vk.KHR_xlib_surface = GLFW_TRUE;

View File

@ -50,7 +50,7 @@ if (VULKAN_FOUND)
add_executable(vulkan WIN32 vulkan.c ${ICON}) add_executable(vulkan WIN32 vulkan.c ${ICON})
target_include_directories(vulkan PRIVATE "${VULKAN_INCLUDE_DIR}") target_include_directories(vulkan PRIVATE "${VULKAN_INCLUDE_DIR}")
if (NOT GLFW_VULKAN_STATIC) if (NOT GLFW_VULKAN_STATIC)
target_link_libraries(vulkan "${VULKAN_LIBRARY}") target_link_libraries(vulkan "${VULKAN_LIBRARY}" ${GLFW_VULKAN_DEPS})
endif() endif()
list(APPEND WINDOWS_BINARIES vulkan) list(APPEND WINDOWS_BINARIES vulkan)
endif() endif()