From 589247d01f67900c09acd5260046c2ac881dccd4 Mon Sep 17 00:00:00 2001 From: SergeyMild <> Date: Sun, 24 May 2026 20:02:02 +0300 Subject: [PATCH] macos: fix Vulkan/Metal renderer on macOS 26+ and bump MoltenVK Four related changes that together let the Vulkan backend boot a game on Apple Silicon Macs running macOS 26.x. Each is needed independently; the combination was tested on a Mac16,6 (M4 Max) / macOS 26.4.1. CMakeModules/DownloadExternals.cmake: Bump bundled MoltenVK from v1.2.9 to v1.4.1. v1.2.9 does not recognize macOS 26 surface objects and traps with an Objective-C "unrecognized selector" inside vkGetPhysicalDeviceSurfaceCapabilitiesKHR. src/video_core/renderer_vulkan/vk_instance.cpp: Disable VK_EXT_tooling_info on MoltenVK. MoltenVK advertises the extension but does not expose vkGetPhysicalDeviceToolPropertiesEXT through its dispatcher, so the call inside CollectToolingInfo() trips a vulkan-hpp assertion in Debug and dereferences NULL in Release. src/citra_qt/bootmanager.cpp: Force-install a fresh CAMetalLayer on the NSView before handing it to createMetalSurfaceEXT. Qt 6.11's QWindow::MetalSurface does not always back the view with a CAMetalLayer on macOS 26, and MoltenVK 1.3+ aborts in MVKSurface::getNaturalExtent() if the layer is not the right class. Implemented via objc_msgSend so no .mm conversion is needed. src/citra_meta/CMakeLists.txt: Gate LaunchScreen.storyboard behind 'if (IOS)'. The storyboard is iOS-only and previously broke the Xcode generator on macOS with "iOS storyboards do not support target device type 'mac'." --- CMakeModules/DownloadExternals.cmake | 2 +- src/citra_meta/CMakeLists.txt | 4 +++- src/citra_qt/bootmanager.cpp | 17 +++++++++++++++-- src/video_core/renderer_vulkan/vk_instance.cpp | 3 ++- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CMakeModules/DownloadExternals.cmake b/CMakeModules/DownloadExternals.cmake index a76d95ae1..1c40448f0 100644 --- a/CMakeModules/DownloadExternals.cmake +++ b/CMakeModules/DownloadExternals.cmake @@ -181,7 +181,7 @@ function(download_moltenvk) set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar") if (NOT EXISTS ${MOLTENVK_DIR}) if (NOT EXISTS ${MOLTENVK_TAR}) - file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-all.tar + file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-all.tar ${MOLTENVK_TAR} SHOW_PROGRESS) endif() diff --git a/src/citra_meta/CMakeLists.txt b/src/citra_meta/CMakeLists.txt index 156799b03..5d477bf87 100644 --- a/src/citra_meta/CMakeLists.txt +++ b/src/citra_meta/CMakeLists.txt @@ -15,9 +15,11 @@ if (APPLE) set(DIST_DIR "../../dist/apple") set(APPLE_RESOURCES "${DIST_DIR}/azahar.icns" - "${DIST_DIR}/LaunchScreen.storyboard" "${DIST_DIR}/launch_logo.png" ) + if (IOS) + list(APPEND APPLE_RESOURCES "${DIST_DIR}/LaunchScreen.storyboard") + endif() target_sources(citra_meta PRIVATE ${APPLE_RESOURCES}) # Define app bundle metadata. diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 39649b8bf..9fbe0bae7 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -420,8 +420,21 @@ static Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window // Our Win32 Qt external doesn't have the private API. wsi.render_surface = reinterpret_cast(window->winId()); #elif defined(__APPLE__) - wsi.render_surface = reinterpret_cast(objc_msgSend)( - reinterpret_cast(window->winId()), sel_registerName("layer")); + // Qt's MetalSurface QWindow on macOS does not always back the NSView + // with a CAMetalLayer immediately; MoltenVK 1.3+ aborts in + // MVKSurface::getNaturalExtent() if the layer is not CAMetalLayer. + // Force-install a fresh CAMetalLayer on the view. + { + id view = reinterpret_cast(window->winId()); + Class metal_layer_class = objc_getClass("CAMetalLayer"); + id metal_layer = reinterpret_cast(objc_msgSend)( + metal_layer_class, sel_registerName("layer")); + reinterpret_cast(objc_msgSend)( + view, sel_registerName("setLayer:"), metal_layer); + reinterpret_cast(objc_msgSend)( + view, sel_registerName("setWantsLayer:"), YES); + wsi.render_surface = reinterpret_cast(metal_layer); + } #else QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); wsi.display_connection = pni->nativeResourceForWindow("display", window); diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index d86cc0f67..6afa4a84f 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -451,7 +451,8 @@ bool Instance::CreateDevice() { image_format_list = add_extension(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME); shader_stencil_export = add_extension(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME); external_memory_host = add_extension(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME); - tooling_info = add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME); + tooling_info = add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME, is_moltenvk, + "function pointer is not exposed by MoltenVK"); const bool has_timeline_semaphores = add_extension(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME, is_qualcomm || is_turnip, "it is broken on Qualcomm drivers");