iOS/tvOS: Remove Vulkan (MoltenVK), Metal only

The iOS and tvOS apps no longer link MoltenVK, and the ios/MoltenVK
submodule is gone. With that, the statically linked Vulkan path
(PPSSPP_VK_STATIC) has no users and is removed. VulkanMayBeAvailable()
returns false in the iOS app, so a config that says Vulkan falls back to
Metal, which is now the tvOS default too. macOS keeps Vulkan.

The graphics settings hide the backend selector when there's only one
backend to pick, using a new SettingItem::Visible() (hidden items are
also left out of search results).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-11-07 13:26:32 +01:00
1 parent 80adb8baf9
commit 5ad6770e30
18 files changed
+58 -113

No files matched your search

-3
View File
@@ -44,9 +44,6 @@
[submodule "ext/OpenXR-SDK"]
path = ext/OpenXR-SDK
url = https://github.com/KhronosGroup/OpenXR-SDK.git
[submodule "ios/MoltenVK"]
path = ios/MoltenVK
url = https://github.com/hrydgard/ppsspp-moltenvk.git
[submodule "ext/lua"]
path = ext/lua
url = https://github.com/hrydgard/ppsspp-lua.git
+1 -16
View File
@@ -323,7 +323,7 @@ elseif(NOT OPENGL_LIBRARIES AND USING_GLES2)
set(OPENGL_LIBRARIES GLESv2 EGL)
endif()
# The iOS app is Vulkan (MoltenVK) only. Must match PPSSPP_API_ANY_GL in ppsspp_config.h.
# The iOS app is Metal only. Must match PPSSPP_API_ANY_GL in ppsspp_config.h.
if(IOS AND NOT LIBRETRO)
set(NO_OPENGL ON)
endif()
@@ -1566,11 +1566,6 @@ if(IOS AND NOT LIBRETRO)
if(IOS_DEBUG)
file(INSTALL pspautotests DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/assets)
endif()
if(TVOS)
set(MOLTENVK_SLICE tvos-arm64)
else()
set(MOLTENVK_SLICE ios-arm64)
endif()
set(RSRC_XIB_FILES "Launch Screen.storyboard" ${CMAKE_CURRENT_SOURCE_DIR}/ios/assets.xcassets)
set_source_files_properties(${RSRC_XIB_FILES}
@@ -1584,15 +1579,6 @@ if(IOS AND NOT LIBRETRO)
set(APP_DIR_NAME "$<TARGET_FILE_DIR:PPSSPP>")
#endif()
# Can't figure out properly using .xcframework from CMake, so just linking directly to the .a file.
# The simulator renders with Metal only, and loads no Vulkan (see PPSSPP_VK_STATIC).
if(IOS_PLATFORM STREQUAL "SIMULATOR")
set(MOLTENVK_LIB "")
else()
set(MOLTENVK_LIB "${CMAKE_CURRENT_SOURCE_DIR}/ios/MoltenVK/MoltenVK.xcframework/${MOLTENVK_SLICE}/libMoltenVK.a")
endif()
target_link_libraries(${TargetBin} ${MOLTENVK_LIB})
set(MACOSX_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET})
set(PRODUCT_NAME "PPSSPP")
if(TVOS)
@@ -1697,7 +1683,6 @@ if(IOS AND NOT LIBRETRO)
# PPSSPPHeadless as an app of its own (org.ppsspp.headless), for running frame dumps on a
# device. Signed for development, so devicectl can copy files in and out of its container.
target_link_libraries(PPSSPPHeadless
${MOLTENVK_LIB}
"-framework Foundation -framework UIKit -framework CoreGraphics -framework CoreText -framework QuartzCore -framework Metal -framework IOSurface -framework AudioToolbox -framework AVFoundation -framework CoreMedia -framework CoreVideo -framework UniformTypeIdentifiers"
)
if(NOT ICONV_LIBRARY)
+1 -3
View File
@@ -251,9 +251,7 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) {
inst_info.enabledExtensionCount = (uint32_t)instance_extensions_enabled_.size();
inst_info.ppEnabledExtensionNames = instance_extensions_enabled_.size() ? instance_extensions_enabled_.data() : nullptr;
#if PPSSPP_VK_STATIC
inst_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
#elif PPSSPP_PLATFORM(MAC)
#if PPSSPP_PLATFORM(MAC)
// Through the Khronos loader (like the SDK's, for the validation layers), MoltenVK is only
// listed if we ask for portability drivers.
if (IsInstanceExtensionAvailable(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) {
+7 -30
View File
@@ -43,7 +43,6 @@
#endif
namespace PPSSPP_VK {
#if !PPSSPP_VK_STATIC
PFN_vkCreateInstance vkCreateInstance;
PFN_vkDestroyInstance vkDestroyInstance;
PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
@@ -245,14 +244,11 @@ PFN_vkCreateRenderPass2 vkCreateRenderPass2;
PFN_vkWaitForPresentKHR vkWaitForPresentKHR;
PFN_vkGetPastPresentationTimingGOOGLE vkGetPastPresentationTimingGOOGLE;
PFN_vkGetRefreshCycleDurationGOOGLE vkGetRefreshCycleDurationGOOGLE;
#endif
} // namespace PPSSPP_VK
using namespace PPSSPP_VK;
#if PPSSPP_VK_STATIC
// Statically linked MoltenVK
#elif PPSSPP_PLATFORM(SWITCH)
#if PPSSPP_PLATFORM(SWITCH)
typedef void *VulkanLibraryHandle;
static VulkanLibraryHandle vulkanLibrary;
#define dlsym(x, y) nullptr
@@ -337,10 +333,10 @@ static constexpr DriverBlackListEntry g_gpuNameBlacklist[] = {
};
#endif
#if !defined(_WIN32) && !PPSSPP_VK_STATIC
#if !defined(_WIN32)
static const char * const so_names[] = {
#if PPSSPP_PLATFORM(IOS)
// Only reached by the libretro core and the simulator (which has none). The app links MoltenVK statically.
// Only reached by the libretro core. The app has no MoltenVK.
"@executable_path/Frameworks/libMoltenVK.dylib",
"MoltenVK",
#elif PPSSPP_PLATFORM(MAC)
@@ -360,7 +356,6 @@ static const char * const so_names[] = {
};
#endif
#if !PPSSPP_VK_STATIC
static VulkanLibraryHandle VulkanLoadLibrary(std::string *errorString) {
#if PPSSPP_PLATFORM(SWITCH)
// Always unavailable, for now.
@@ -418,9 +413,7 @@ static VulkanLibraryHandle VulkanLoadLibrary(std::string *errorString) {
return lib;
#endif
}
#endif
#if !PPSSPP_VK_STATIC
static void VulkanFreeLibrary(VulkanLibraryHandle &h) {
if (h) {
#if PPSSPP_PLATFORM(SWITCH)
@@ -433,7 +426,6 @@ static void VulkanFreeLibrary(VulkanLibraryHandle &h) {
h = nullptr;
}
}
#endif
void VulkanSetAvailable(bool available) {
INFO_LOG(Log::G3D, "Setting Vulkan availability to true");
@@ -446,7 +438,10 @@ void VulkanSetNativeLibDir(std::string_view nativeLibDir) {
}
bool VulkanMayBeAvailable() {
#if PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(IOS_SIMULATOR)
#if PPSSPP_PLATFORM(IOS) && !defined(__LIBRETRO__)
// The app renders with Metal, and has no MoltenVK.
return false;
#elif PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(IOS_SIMULATOR)
// The deployment target (15.0) is above anything MoltenVK doesn't support.
g_vulkanAvailabilityChecked = true;
g_vulkanMayBeAvailable = true;
@@ -681,11 +676,6 @@ bail:
}
bool VulkanLoad(std::string *errorStr) {
#if PPSSPP_VK_STATIC
INFO_LOG(Log::G3D, "iOS: Vulkan doesn't need loading");
return true;
#else
if (!vulkanLibrary) {
vulkanLibrary = VulkanLoadLibrary(errorStr);
if (!vulkanLibrary) {
@@ -711,15 +701,9 @@ bool VulkanLoad(std::string *errorStr) {
VulkanFreeLibrary(vulkanLibrary);
return false;
}
#endif
}
bool VulkanLoadFromGetInstanceProcAddr(VkInstance instance, PFN_vkGetInstanceProcAddr getInstanceProcAddr) {
#if PPSSPP_VK_STATIC
// MoltenVK is linked statically, the functions are already there - there are no function pointers to fill in.
INFO_LOG(Log::G3D, "iOS: Vulkan doesn't need loading");
return true;
#else
vkGetInstanceProcAddr = getInstanceProcAddr;
vkCreateInstance = (PFN_vkCreateInstance)getInstanceProcAddr(nullptr, "vkCreateInstance");
// Per the Vulkan spec, vkGetDeviceProcAddr is not one of the handful of commands queryable with a
@@ -737,11 +721,9 @@ bool VulkanLoadFromGetInstanceProcAddr(VkInstance instance, PFN_vkGetInstancePro
ERROR_LOG(Log::G3D, "VulkanLoadFromGetInstanceProcAddr: Failed to load Vulkan base functions");
return false;
}
#endif
}
void VulkanLoadInstanceFunctions(VkInstance instance, const VulkanExtensions &enabledExtensions, uint32_t vulkanInstanceApiVersion) {
#if !PPSSPP_VK_STATIC
INFO_LOG(Log::G3D, "Loading Vulkan instance functions. Instance API version: %08x (%d.%d.%d)", vulkanInstanceApiVersion, VK_API_VERSION_MAJOR(vulkanInstanceApiVersion), VK_API_VERSION_MINOR(vulkanInstanceApiVersion), VK_API_VERSION_PATCH(vulkanInstanceApiVersion));
// OK, let's use the above functions to get the rest.
LOAD_INSTANCE_FUNC(instance, vkDestroyInstance);
@@ -810,14 +792,12 @@ void VulkanLoadInstanceFunctions(VkInstance instance, const VulkanExtensions &en
}
INFO_LOG(Log::G3D, "Vulkan instance functions loaded.");
#endif
}
// On some implementations, loading functions (that have Device as their first parameter) via vkGetDeviceProcAddr may
// increase performance - but then these function pointers will only work on that specific device. Thus, this loader is not very
// good for multi-device - not likely we'll ever try that anyway though.
void VulkanLoadDeviceFunctions(VkDevice device, const VulkanExtensions &enabledExtensions, uint32_t vulkanDeviceApiVersion) {
#if !PPSSPP_VK_STATIC
INFO_LOG(Log::G3D, "Loading Vulkan device functions. Device API version: %08x (%d.%d.%d)", vulkanDeviceApiVersion, VK_API_VERSION_MAJOR(vulkanDeviceApiVersion), VK_API_VERSION_MINOR(vulkanDeviceApiVersion), VK_API_VERSION_PATCH(vulkanDeviceApiVersion));
LOAD_DEVICE_FUNC(device, vkQueueSubmit);
@@ -958,13 +938,10 @@ void VulkanLoadDeviceFunctions(VkDevice device, const VulkanExtensions &enabledE
LOAD_DEVICE_FUNC_CORE(device, vkGetDeviceBufferMemoryRequirements, vkGetDeviceBufferMemoryRequirementsKHR, VK_API_VERSION_1_3);
LOAD_DEVICE_FUNC_CORE(device, vkGetDeviceImageMemoryRequirements, vkGetDeviceImageMemoryRequirementsKHR, VK_API_VERSION_1_3);
}
#endif
}
void VulkanFree() {
#if !PPSSPP_VK_STATIC
VulkanFreeLibrary(vulkanLibrary);
#endif
}
const char *VulkanResultToString(VkResult res) {
+2 -9
View File
@@ -32,14 +32,9 @@
#define VK_USE_PLATFORM_METAL_EXT
#endif
// The iOS app links MoltenVK statically and calls Vulkan directly. Everywhere else (including the
// iOS libretro core), we load the functions at runtime into the pointers below. The simulator build
// has no MoltenVK at all (it uses Metal), so it takes the runtime path, which finds no library.
#if PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(IOS_SIMULATOR) && !defined(__LIBRETRO__)
#define PPSSPP_VK_STATIC 1
#else
// We load the functions at runtime into the pointers below. The iOS app has no MoltenVK (it uses
// Metal), so there this finds no library.
#define VK_NO_PROTOTYPES
#endif
#define VK_ENABLE_BETA_EXTENSIONS 1 // VK_KHR_portability_subset
#include "ext/vulkan/vulkan.h"
@@ -51,7 +46,6 @@
#endif
namespace PPSSPP_VK {
#if !PPSSPP_VK_STATIC
// Putting our own Vulkan function pointers in a namespace ensures that ppsspp_libretro.so doesn't collide with libvulkan.so.
extern PFN_vkCreateInstance vkCreateInstance;
extern PFN_vkDestroyInstance vkDestroyInstance;
@@ -252,7 +246,6 @@ extern PFN_vkGetMemoryHostPointerPropertiesEXT vkGetMemoryHostPointerPropertiesE
extern PFN_vkWaitForPresentKHR vkWaitForPresentKHR;
extern PFN_vkGetPastPresentationTimingGOOGLE vkGetPastPresentationTimingGOOGLE;
extern PFN_vkGetRefreshCycleDurationGOOGLE vkGetRefreshCycleDurationGOOGLE;
#endif // !PPSSPP_VK_STATIC
} // namespace PPSSPP_VK
// For fast extension-enabled checks.
@@ -900,7 +900,6 @@ void VulkanRenderManager::RenderThreadFunc() {
void VulkanRenderManager::PresentWaitThreadFunc() {
SetCurrentThreadName("PresentWait");
#if !PPSSPP_VK_STATIC
_dbg_assert_(vkWaitForPresentKHR != nullptr);
uint64_t waitedId = frameIdGen_;
@@ -917,7 +916,6 @@ void VulkanRenderManager::PresentWaitThreadFunc() {
}
_dbg_assert_(waitedId <= frameIdGen_);
}
#endif
INFO_LOG(Log::G3D, "Leaving PresentWaitThreadFunc()");
}
-3
View File
@@ -661,9 +661,6 @@ static int DefaultGPUBackend() {
return (int)GPUBackend::OPENGL;
#endif
#elif PPSSPP_PLATFORM(TVOS) && !defined(__LIBRETRO__)
// Metal hasn't been tried on tvOS yet.
return (int)GPUBackend::VULKAN;
#elif PPSSPP_PLATFORM(IOS) && !defined(__LIBRETRO__)
return (int)GPUBackend::METAL;
#endif
+12 -1
View File
@@ -139,6 +139,17 @@ static void BuildCardboardVRPage(SettingsPage *page) {
page->Setting(&config.iCardboardYShift).Enabled(&config.bEnableCardboardVR);
}
// Asking about Vulkan can load its driver, so that's done last, and only when it decides the answer.
static bool HasBackendChoice() {
int count = 0;
for (GPUBackend backend : { GPUBackend::OPENGL, GPUBackend::DIRECT3D11, GPUBackend::METAL }) {
if (g_Config.IsBackendEnabled(backend)) {
count++;
}
}
return count >= 2 || (count == 1 && g_Config.IsBackendEnabled(GPUBackend::VULKAN));
}
void BuildGraphicsSettings(SettingsPage *page) {
using namespace UI;
SettingsPageHost *host = page->Host();
@@ -149,7 +160,7 @@ void BuildGraphicsSettings(SettingsPage *page) {
page->Header("Rendering");
#if !PPSSPP_PLATFORM(UWP)
page->Setting(&g_Config.iGPUBackend).Setup([host](View *view) {
page->Setting(&g_Config.iGPUBackend).Visible(HasBackendChoice()).Setup([host](View *view) {
PopupMultiChoice *renderingBackendChoice = static_cast<PopupMultiChoice *>(view);
renderingBackendChoice->SetPreOpenCallback([host](UI::PopupMultiChoice *choice) {
// Don't filter until the last possible moment, since it involves trying to initialize Vulkan, if we were
+7 -1
View File
@@ -430,12 +430,18 @@ static UI::View *CreateSettingView(const SettingItem &item, I18NCat pageCat, UI:
clickable->SetHintText(item.hint.Translate(pageCat));
} else {
// Custom views that can't show a hint get it below them.
parent->Add(new SettingHint(item.hint.Translate(pageCat), view));
UI::View *hint = parent->Add(new SettingHint(item.hint.Translate(pageCat), view));
if (!item.visible) {
hint->SetVisibility(UI::V_GONE);
}
}
}
if (item.setup) {
item.setup(view);
}
if (!item.visible) {
view->SetVisibility(UI::V_GONE);
}
return view;
}
+3
View File
@@ -154,6 +154,8 @@ struct SettingItem {
bool *disabledPtr = nullptr;
std::function<bool()> enabledFunc;
SettingText disabledReason;
// A hidden item still gets its view (it's gone from the layout), so views stay in step with items.
bool visible = true;
// Called on click (Bool, Choice), choice (choices) or change (sliders).
std::function<void(UI::EventParams &)> onChange;
// From the config, for settings described there. Called before onChange.
@@ -168,6 +170,7 @@ struct SettingItem {
SettingItem &Enabled(bool *ptr) { enabledPtr = ptr; return *this; }
SettingItem &Disabled(bool *ptr) { disabledPtr = ptr; return *this; }
SettingItem &EnabledFunc(std::function<bool()> func) { enabledFunc = std::move(func); return *this; }
SettingItem &Visible(bool value) { visible = value; return *this; }
// Shown when the user tries to use the item while it's disabled (see ShowWhyViewDisabled).
SettingItem &DisabledReason(SettingText text) { disabledReason = text; return *this; }
+1 -1
View File
@@ -426,7 +426,7 @@ bool SettingsPagesDialogScreen::SearchPage(UI::ViewGroup *parent, int settingsTa
if (!item.hint.empty() && SettingTextMatches(item.hint, cat, searchText_)) {
matches = true;
}
show[i] = matches || sectionMatches;
show[i] = item.visible && (matches || sectionMatches);
break;
}
}
+3 -3
View File
@@ -1,12 +1,12 @@
# The Metal backend
A native Metal backend for Mac and iOS (tvOS too), alongside OpenGL, D3D11 and Vulkan. It's selected as
`GPUBackend::METAL` (`--graphics=metal`). It's the default on iOS since 2026-10-22; Vulkan through MoltenVK
stays the default on Mac and tvOS. Written 2026-10-19; file references will drift.
`GPUBackend::METAL` (`--graphics=metal`). It's the only backend in the iOS and tvOS apps, which no longer ship
MoltenVK; Vulkan through MoltenVK stays the default on Mac. Written 2026-10-19; file references will drift.
**Why:** programmable blending. Apple GPUs let a fragment shader read the color it's about to blend with
(`[[color(0)]]`), so PSP blend modes that need shader blending no longer copy the framebuffer for every
draw. MoltenVK can't expose that. Removing the MoltenVK dependency is a smaller benefit, and comes later.
draw. MoltenVK can't expose that.
## Where things are
-5
View File
@@ -117,11 +117,6 @@ static bool g_launched = false;
return;
}
// Without a window, iOS mirrors the phone.
// The running backend: the setting is already the new one while the restart prompt for a change is up.
if (GetGPUBackend() != GPUBackend::METAL && GetGPUBackend() != GPUBackend::VULKAN) {
INFO_LOG(Log::G3D, "External display: mirroring, as the backend can't present to it");
return;
}
if (g_Config.iExternalDisplay != (int)ExternalDisplayMode::SHOW_GAME) {
INFO_LOG(Log::G3D, "External display: mirroring, as set");
return;
Submodule ios/MoltenVK deleted from b77380b5c8.
-7
View File
@@ -1,10 +1,3 @@
Updating with a self-built MoltenVK
===================================
Probably won't be needed again.
cp -r ../dev/build-molten/MoltenVK/Package/Release/MoltenVK/static/MoltenVK.xcframework ios/MoltenVK
The Old iOS Build Instructions
==============================
+2 -2
View File
@@ -8,7 +8,7 @@
#if PPSSPP_PLATFORM(TVOS)
// The one view controller. Renders with Vulkan (MoltenVK) to a CAMetalLayer.
// The one view controller. Renders with Metal to a CAMetalLayer.
// A GCEventViewController, so controller input (the Siri Remote included) reaches us only through
// GCController, instead of also driving UIKit's focus engine.
@interface PPSSPPViewController : GCEventViewController
@@ -20,7 +20,7 @@
#import "CameraHelper.h"
#import "LocationHelper.h"
// The one view controller. Renders with Vulkan (MoltenVK) to a CAMetalLayer.
// The one view controller. Renders with Metal to a CAMetalLayer.
@interface PPSSPPViewController : UIViewController<
PHPickerViewControllerDelegate,
CameraFrameDelegate, LocationHandlerDelegate,
+18 -25
View File
@@ -13,7 +13,6 @@
#include <optional>
#include <thread>
#include "Common/GPU/GraphicsContext.h"
#include "Common/GPU/Vulkan/VulkanGraphicsContext.h"
#include "Common/GPU/Metal/MetalGraphicsContext.h"
#include "Common/System/Request.h"
#include "Common/System/System.h"
@@ -108,7 +107,7 @@ static void ApplyPendingResolution() {
}
}
static void VulkanRenderLoop(GraphicsContext *graphicsContext, CAMetalLayer *metalLayer) {
static void RenderLoop(GraphicsContext *graphicsContext, CAMetalLayer *metalLayer) {
// Only a provisional name, RunGraphicsLoop renames the thread once it knows its role.
SetCurrentThreadName("RenderLoop");
_assert_(graphicsContext);
@@ -239,10 +238,10 @@ static int GetPickerRequestId(id picker) {
_dbg_assert_(sharedViewController != nil);
sharedViewController = nil;
g_Config.Save("shutdown vk");
g_Config.Save("shutdown");
// Normally already done by willResignActive, but not when the app is terminated.
[self requestExitVulkanRenderLoop];
[self requestExitRenderLoop];
if (graphicsContext) {
graphicsContext->ShutdownAPI();
@@ -252,24 +251,24 @@ static int GetPickerRequestId(id picker) {
}
- (void)dealloc {
INFO_LOG(Log::System, "dealloc VK");
INFO_LOG(Log::System, "dealloc");
}
- (bool)runVulkanRenderLoop {
INFO_LOG(Log::G3D, "runVulkanRenderLoop");
- (bool)runRenderLoop {
INFO_LOG(Log::G3D, "runRenderLoop");
if (!graphicsContext) {
ERROR_LOG(Log::G3D, "runVulkanRenderLoop: Tried to enter without a created graphics context.");
ERROR_LOG(Log::G3D, "runRenderLoop: Tried to enter without a created graphics context.");
return false;
}
if (g_renderLoopThread.joinable()) {
if (renderLoopRunning) {
ERROR_LOG(Log::G3D, "runVulkanRenderLoop: Already running");
ERROR_LOG(Log::G3D, "runRenderLoop: Already running");
return false;
}
// The previous thread gave up by itself (failed surface init). Reap it so we can try again.
WARN_LOG(Log::G3D, "runVulkanRenderLoop: Joining a render thread that had already exited");
WARN_LOG(Log::G3D, "runRenderLoop: Joining a render thread that had already exited");
g_renderLoopThread.join();
g_renderLoopThread = std::thread();
}
@@ -279,12 +278,12 @@ static int GetPickerRequestId(id picker) {
CAMetalLayer *metalLayer = (CAMetalLayer *)self.view.layer;
// Set before the thread exists, so an exit request can't slip in ahead of it.
renderLoopRunning = true;
g_renderLoopThread = std::thread(VulkanRenderLoop, graphicsContext, metalLayer);
g_renderLoopThread = std::thread(RenderLoop, graphicsContext, metalLayer);
return true;
}
- (void)requestExitVulkanRenderLoop {
INFO_LOG(Log::G3D, "requestExitVulkanRenderLoop");
- (void)requestExitRenderLoop {
INFO_LOG(Log::G3D, "requestExitRenderLoop");
// Don't go by renderLoopRunning - the thread might have bailed by itself, and still needs joining.
if (!g_renderLoopThread.joinable()) {
@@ -343,11 +342,11 @@ static int GetPickerRequestId(id picker) {
// Spin up the render thread, which in turn starts the emu thread.
[self updateResolutionWithView:self.view];
[self runVulkanRenderLoop];
[self runRenderLoop];
}
- (void)willResignActive {
[self requestExitVulkanRenderLoop];
[self requestExitRenderLoop];
#if !PPSSPP_PLATFORM(TVOS)
g_touchTracker.CancelAll();
@@ -631,11 +630,7 @@ static int GetPickerRequestId(id picker) {
keyboardInputView_ = [[KeyboardInputView alloc] initWithFrame:CGRectZero];
[self.view addSubview:keyboardInputView_];
if (g_Config.iGPUBackend == (int)GPUBackend::METAL) {
graphicsContext = new MetalGraphicsContext();
} else {
graphicsContext = new VulkanGraphicsContext();
}
graphicsContext = new MetalGraphicsContext();
std::string errorMessage;
if (!graphicsContext->InitAPI(nullptr, &g_Config.sVulkanDevice, &errorMessage)) {
delete graphicsContext;
@@ -650,11 +645,9 @@ static int GetPickerRequestId(id picker) {
// From iOS 27, an external display scene only goes to apps that register for one like this. Earlier
// versions go by the Info.plist role instead. Without the scene, iOS mirrors the phone.
if (@available(iOS 27.0, *)) {
if (g_Config.iGPUBackend == (int)GPUBackend::METAL || g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {
UISceneConfiguration *sceneConfiguration = [UISceneConfiguration new];
sceneConfiguration.delegateClass = [ExternalDisplaySceneDelegate class];
self.externalDisplayRegistration = [self registerSceneAccessory:[UISceneAccessory externalNonInteractiveSceneAccessoryWithConfiguration:sceneConfiguration]];
}
UISceneConfiguration *sceneConfiguration = [UISceneConfiguration new];
sceneConfiguration.delegateClass = [ExternalDisplaySceneDelegate class];
self.externalDisplayRegistration = [self registerSceneAccessory:[UISceneAccessory externalNonInteractiveSceneAccessoryWithConfiguration:sceneConfiguration]];
}
#endif
+1 -1
View File
@@ -5,7 +5,7 @@
// main.mm: JIT enablement, the System_ functions, starting the next step
// AppDelegate.mm: Audio session interruptions, deep link and library export helpers
// SceneDelegate.mm: Runs NativeInit, creates the window and the view controller
// ViewController.mm: The view controller. Rendering (Vulkan to a CAMetalLayer), input, keyboard, pickers and sizing
// ViewController.mm: The view controller. Rendering (Metal to a CAMetalLayer), input, keyboard, pickers and sizing
#import <UIKit/UIKit.h>
#import <dlfcn.h>