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'."
This commit is contained in:
SergeyMild 2026-05-24 20:02:02 +03:00
parent f0bc64d967
commit 589247d01f
4 changed files with 21 additions and 5 deletions

View file

@ -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()

View file

@ -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.

View file

@ -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<void*>(window->winId());
#elif defined(__APPLE__)
wsi.render_surface = reinterpret_cast<void* (*)(id, SEL)>(objc_msgSend)(
reinterpret_cast<id>(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<id>(window->winId());
Class metal_layer_class = objc_getClass("CAMetalLayer");
id metal_layer = reinterpret_cast<id (*)(Class, SEL)>(objc_msgSend)(
metal_layer_class, sel_registerName("layer"));
reinterpret_cast<void (*)(id, SEL, id)>(objc_msgSend)(
view, sel_registerName("setLayer:"), metal_layer);
reinterpret_cast<void (*)(id, SEL, BOOL)>(objc_msgSend)(
view, sel_registerName("setWantsLayer:"), YES);
wsi.render_surface = reinterpret_cast<void*>(metal_layer);
}
#else
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
wsi.display_connection = pni->nativeResourceForWindow("display", window);

View file

@ -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");