diff --git a/CMake/modules/FindVulkan.cmake b/CMake/modules/FindVulkan.cmake index d3a664a8..2ea1200f 100644 --- a/CMake/modules/FindVulkan.cmake +++ b/CMake/modules/FindVulkan.cmake @@ -20,6 +20,13 @@ if (WIN32) "$ENV{VULKAN_SDK}/Bin32" "$ENV{VK_SDK_PATH}/Bin32") 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() find_path(VULKAN_INCLUDE_DIR NAMES vulkan/vulkan.h HINTS "$ENV{VULKAN_SDK}/include") diff --git a/CMakeLists.txt b/CMakeLists.txt index 5215ca80..388a555f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -129,6 +129,15 @@ if (MINGW) 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 #-------------------------------------------------------------------- @@ -158,7 +167,10 @@ endif() #-------------------------------------------------------------------- if (GLFW_VULKAN_STATIC) 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() if (BUILD_SHARED_LIBS OR GLFW_BUILD_EXAMPLES OR GLFW_BUILD_TESTS) message(FATAL_ERROR "Vulkan loader static library not found") diff --git a/README.md b/README.md index 00be04ea..68d257f9 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ information on what to include when reporting a bug. - Bugfix: `glfwGetInstanceProcAddress` returned `NULL` for `vkGetInstanceProcAddr` when `_GLFW_VULKAN_STATIC` was enabled - [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: Window creation failed to set first responder (#876,#883) - [EGL] Added support for `EGL_KHR_get_all_proc_addresses` (#871) diff --git a/docs/compat.dox b/docs/compat.dox index 624dc3b1..c54bdd91 100644 --- a/docs/compat.dox +++ b/docs/compat.dox @@ -185,10 +185,11 @@ a non-default value will cause @ref glfwCreateWindow to fail and the @section compat_vulkan Vulkan loader and API -GLFW uses the standard system-wide Vulkan loader to access the Vulkan API. -This should be installed by graphics drivers and Vulkan SDKs. If this is not -available, @ref glfwVulkanSupported will return `GLFW_FALSE` and all other -Vulkan-related functions will fail with an @ref GLFW_API_UNAVAILABLE error. +By default, GLFW uses the standard system-wide Vulkan loader to access the +Vulkan API on all platforms except macOS. This is installed by both graphics +drivers and Vulkan SDKs. If the loader is not found, @ref glfwVulkanSupported +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 @@ -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 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 `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 @@ -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 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. - */ diff --git a/docs/news.dox b/docs/news.dox index 33019157..707bd55a 100644 --- a/docs/news.dox +++ b/docs/news.dox @@ -16,6 +16,12 @@ GLFW now supports querying the platform dependent scancode of any key with @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 diff --git a/docs/vulkan.dox b/docs/vulkan.dox index e704222a..32b96463 100644 --- a/docs/vulkan.dox +++ b/docs/vulkan.dox @@ -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. 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 -libraries, it also provides the validation layers necessary for development. +the [LunarG Vulkan SDK](https://vulkan.lunarg.com/) for Windows and Linux or +[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. 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 +@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 To include the Vulkan header, define [GLFW_INCLUDE_VULKAN](@ref build_macros) diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index c4841769..db2bd8a9 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -176,7 +176,11 @@ extern "C" { #endif #endif #if defined(GLFW_INCLUDE_VULKAN) - #include + #if defined(__APPLE__) + #include + #else + #include + #endif #endif #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 * 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 * should not free it yourself. It is guaranteed to be valid only until the * library is terminated. @@ -4305,6 +4312,10 @@ GLFWAPI GLFWvkproc glfwGetInstanceProcAddress(VkInstance instance, const char* p * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * 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 * 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 * 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 * synchronization details of Vulkan objects, see the Vulkan specification. * diff --git a/src/cocoa_platform.h b/src/cocoa_platform.h index 6147e538..fcacf661 100644 --- a/src/cocoa_platform.h +++ b/src/cocoa_platform.h @@ -39,6 +39,18 @@ typedef void* id; #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 "cocoa_joystick.h" #include "nsgl_context.h" @@ -74,6 +86,7 @@ typedef struct _GLFWwindowNS id object; id delegate; id view; + id layer; GLFWbool maximized; diff --git a/src/cocoa_window.m b/src/cocoa_window.m index 5db64cc8..41c7dbd5 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -431,6 +431,16 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; return YES; } +- (BOOL)wantsUpdateLayer +{ + return YES; +} + +- (id)makeBackingLayer +{ + return window->ns.layer; +} + - (void)cursorUpdate:(NSEvent *)event { updateCursorImage(window); @@ -1653,13 +1663,18 @@ const char* _glfwPlatformGetClipboardString(_GLFWwindow* window) 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, VkPhysicalDevice device, uint32_t queuefamily) { - return GLFW_FALSE; + return GLFW_TRUE; } VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, @@ -1667,7 +1682,57 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, const VkAllocationCallbacks* allocator, 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; +#endif } diff --git a/src/internal.h b/src/internal.h index 77af1d1c..3dc33691 100644 --- a/src/internal.h +++ b/src/internal.h @@ -110,6 +110,7 @@ typedef enum VkStructureType VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000, VK_STRUCTURE_TYPE_MIR_SURFACE_CREATE_INFO_KHR = 1000007000, VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000, + VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000053000, VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF } VkStructureType; @@ -458,6 +459,8 @@ struct _GLFWlibrary GLFWbool KHR_surface; #if defined(_GLFW_WIN32) GLFWbool KHR_win32_surface; +#elif defined(_GLFW_COCOA) + GLFWbool MVK_macos_surface; #elif defined(_GLFW_X11) GLFWbool KHR_xlib_surface; GLFWbool KHR_xcb_surface; diff --git a/src/vulkan.c b/src/vulkan.c index b4e701bd..c39711b6 100644 --- a/src/vulkan.c +++ b/src/vulkan.c @@ -45,17 +45,18 @@ GLFWbool _glfwInitVulkan(int mode) VkExtensionProperties* ep; 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) 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 (mode == _GLFW_REQUIRE_LOADER) @@ -68,8 +69,16 @@ GLFWbool _glfwInitVulkan(int mode) _glfw_dlsym(_glfw.vk.handle, "vkGetInstanceProcAddr"); 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, "Vulkan: Loader does not export vkGetInstanceProcAddr"); +#endif _glfwTerminateVulkan(); return GLFW_FALSE; @@ -119,6 +128,9 @@ GLFWbool _glfwInitVulkan(int mode) #if defined(_GLFW_WIN32) else if (strcmp(ep[i].extensionName, "VK_KHR_win32_surface") == 0) _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) else if (strcmp(ep[i].extensionName, "VK_KHR_xlib_surface") == 0) _glfw.vk.KHR_xlib_surface = GLFW_TRUE; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4a97b038..2d88ec15 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -50,7 +50,7 @@ if (VULKAN_FOUND) add_executable(vulkan WIN32 vulkan.c ${ICON}) target_include_directories(vulkan PRIVATE "${VULKAN_INCLUDE_DIR}") if (NOT GLFW_VULKAN_STATIC) - target_link_libraries(vulkan "${VULKAN_LIBRARY}") + target_link_libraries(vulkan "${VULKAN_LIBRARY}" ${GLFW_VULKAN_DEPS}) endif() list(APPEND WINDOWS_BINARIES vulkan) endif()