Merge pull request #19166 from hrydgard/ios-vulkan-work

Vulkan on iOS using MoltenVK
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2024-05-24 12:12:28 +02:00
commit bef645697a
21 files changed
+927 -146

No files matched your search

+3
View File
@@ -59,3 +59,6 @@
[submodule "ext/OpenXR-SDK"]
path = ext/OpenXR-SDK
url = https://github.com/KhronosGroup/OpenXR-SDK.git
[submodule "ios/MoltenVK"]
path = ios/MoltenVK
url = git@github.com:hrydgard/ppsspp-moltenvk.git
+11 -2
View File
@@ -140,6 +140,8 @@ if(IOS_APP_STORE)
# Set a global default to not generate schemes for each target.
# Makes using XCode sligthly more sane.
set(CMAKE_XCODE_GENERATE_SCHEME NO)
set(CMAKE_XCODE_SCHEME_ENABLE_GPU_API_VALIDATION FALSE)
set(CMAKE_XCODE_SCHEME_ENABLE_GPU_FRAME_CAPTURE_MODE DISABLED)
message("iOS App Store build")
else()
message("iOS sideload build")
@@ -1295,6 +1297,8 @@ elseif(IOS AND NOT LIBRETRO)
ios/ViewControllerCommon.h
ios/ViewController.mm
ios/ViewController.h
ios/ViewControllerMetal.mm
ios/ViewControllerMetal.h
ios/iOSCoreAudio.mm
ios/iOSCoreAudio.h
ios/CameraHelper.mm
@@ -1313,7 +1317,7 @@ elseif(IOS AND NOT LIBRETRO)
Common/Battery/AppleBatteryClient.m
)
set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework MediaPlayer -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL -framework AVFoundation -framework CoreLocation -framework CoreVideo -framework CoreMedia -framework CoreServices" )
set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework MediaPlayer -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL -framework AVFoundation -framework CoreLocation -framework CoreVideo -framework CoreMedia -framework CoreServices -framework Metal -framework IOSurface" )
if(EXISTS "${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks/GameController.framework")
set(nativeExtraLibs ${nativeExtraLibs} "-weak_framework GameController")
endif()
@@ -2879,13 +2883,18 @@ if(IOS AND NOT LIBRETRO)
# This updates the version in the plist.
COMMAND /bin/bash "${CMAKE_SOURCE_DIR}/ios/iosbundle.sh" \"${APP_DIR_NAME}\" "${CMAKE_CURRENT_BINARY_DIR}"
)
# Can't figure out properly using .xcframework from CMake, so just linking directly to the .a file.
target_link_libraries(${TargetBin}
"${CMAKE_CURRENT_SOURCE_DIR}/ios/MoltenVK/MoltenVK.xcframework/ios-arm64/libMoltenVK.a"
)
# https://stackoverflow.com/questions/40664125/cmake-and-code-signing-in-xcode-8-for-ios-projects
set_target_properties(${TargetBin} PROPERTIES
XCODE_GENERATE_SCHEME YES # Avoid the scheme bloat in XCode by only setting it to YES for this target.
RESOURCE "ios/Launch Screen.storyboard"
RESOURCE "ios/Settings.bundle"
RESOURCE "ios/assets.xcassets"
RESOURCE "ext/vulkan/iOS/Frameworks"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER ${BUNDLE_IDENTIFIER}
XCODE_ATTRIBUTE_PRODUCT_NAME ${PRODUCT_NAME}
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME ${ICON_NAME}
+3 -4
View File
@@ -348,6 +348,9 @@ void GLRenderManager::BeginFrame(bool enableProfiling) {
curProgram_ = nullptr;
#endif
// Shouldn't call BeginFrame unless we're in a run state.
_dbg_assert_(runCompileThread_);
int curFrame = GetCurFrame();
FrameTimeData &frameTimeData = frameTimeHistory_.Add(frameIdGen_);
@@ -368,10 +371,6 @@ void GLRenderManager::BeginFrame(bool enableProfiling) {
frameData.readyForFence = false;
}
if (!runCompileThread_) {
WARN_LOG(G3D, "BeginFrame while !run_!");
}
insideFrame_ = true;
}
+15 -2
View File
@@ -183,6 +183,11 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) {
if (EnableInstanceExtension(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME, 0)) {
extensionsLookup_.EXT_swapchain_colorspace = true;
}
#if PPSSPP_PLATFORM(IOS_APP_STORE)
if (EnableInstanceExtension(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, 0)) {
}
#endif
// Validate that all the instance extensions we ask for are actually available.
for (auto ext : instance_extensions_enabled_) {
@@ -206,6 +211,10 @@ 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_PLATFORM(IOS_APP_STORE)
inst_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
#endif
#if SIMULATE_VULKAN_FAILURE == 2
VkResult res = VK_ERROR_INCOMPATIBLE_DRIVER;
#else
@@ -896,7 +905,7 @@ VkResult VulkanContext::ReinitSurface() {
surface_ = VK_NULL_HANDLE;
}
INFO_LOG(G3D, "Creating Vulkan surface for window (%p %p)", winsysData1_, winsysData2_);
INFO_LOG(G3D, "Creating Vulkan surface for window (data1=%p data2=%p)", winsysData1_, winsysData2_);
VkResult retval = VK_SUCCESS;
@@ -1311,7 +1320,11 @@ bool VulkanContext::InitSwapchain() {
VkExtent2D currentExtent { surfCapabilities_.currentExtent };
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkSurfaceCapabilitiesKHR.html
// currentExtent is the current width and height of the surface, or the special value (0xFFFFFFFF, 0xFFFFFFFF) indicating that the surface size will be determined by the extent of a swapchain targeting the surface.
if (currentExtent.width == 0xFFFFFFFFu || currentExtent.height == 0xFFFFFFFFu) {
if (currentExtent.width == 0xFFFFFFFFu || currentExtent.height == 0xFFFFFFFFu
#if PPSSPP_PLATFORM(IOS)
|| currentExtent.width == 0 || currentExtent.height == 0
#endif
) {
_dbg_assert_((bool)cbGetDrawSize_)
if (cbGetDrawSize_) {
currentExtent = cbGetDrawSize_();
+31 -4
View File
@@ -39,6 +39,7 @@
#endif
namespace PPSSPP_VK {
#if !PPSSPP_PLATFORM(IOS_APP_STORE)
PFN_vkCreateInstance vkCreateInstance;
PFN_vkDestroyInstance vkDestroyInstance;
PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
@@ -240,12 +241,14 @@ PFN_vkCreateRenderPass2 vkCreateRenderPass2;
PFN_vkWaitForPresentKHR vkWaitForPresentKHR;
PFN_vkGetPastPresentationTimingGOOGLE vkGetPastPresentationTimingGOOGLE;
PFN_vkGetRefreshCycleDurationGOOGLE vkGetRefreshCycleDurationGOOGLE;
#endif
} // namespace PPSSPP_VK
using namespace PPSSPP_VK;
#if PPSSPP_PLATFORM(SWITCH)
#if PPSSPP_PLATFORM(IOS_APP_STORE)
// Statically linked MoltenVK
#elif PPSSPP_PLATFORM(SWITCH)
typedef void *VulkanLibraryHandle;
static VulkanLibraryHandle vulkanLibrary;
#define dlsym(x, y) nullptr
@@ -280,7 +283,8 @@ static const char * const device_name_blacklist[] = {
#ifndef _WIN32
static const char * const so_names[] = {
#if PPSSPP_PLATFORM(IOS)
#if PPSSPP_PLATFORM(IOS_APP_STORE)
#elif PPSSPP_PLATFORM(IOS)
"@executable_path/Frameworks/libMoltenVK.dylib",
"MoltenVK",
#elif PPSSPP_PLATFORM(MAC)
@@ -295,6 +299,7 @@ static const char * const so_names[] = {
};
#endif
#if !PPSSPP_PLATFORM(IOS_APP_STORE)
static VulkanLibraryHandle VulkanLoadLibrary(std::string *errorString) {
#if PPSSPP_PLATFORM(SWITCH)
// Always unavailable, for now.
@@ -346,11 +351,13 @@ static VulkanLibraryHandle VulkanLoadLibrary(std::string *errorString) {
break;
}
}
}
}
return lib;
#endif
}
#endif
#if !PPSSPP_PLATFORM(IOS_APP_STORE)
static void VulkanFreeLibrary(VulkanLibraryHandle &h) {
if (h) {
#if PPSSPP_PLATFORM(SWITCH)
@@ -363,6 +370,7 @@ static void VulkanFreeLibrary(VulkanLibraryHandle &h) {
h = nullptr;
}
}
#endif
void VulkanSetAvailable(bool available) {
INFO_LOG(G3D, "Setting Vulkan availability to true");
@@ -371,6 +379,12 @@ void VulkanSetAvailable(bool available) {
}
bool VulkanMayBeAvailable() {
#if PPSSPP_PLATFORM(IOS_APP_STORE)
g_vulkanAvailabilityChecked = true;
g_vulkanMayBeAvailable = true;
return true;
#else
// Unsupported in VR at the moment
if (IsVREnabled()) {
return false;
@@ -557,9 +571,15 @@ bail:
WARN_LOG(G3D, "Vulkan with working device not detected.");
}
return g_vulkanMayBeAvailable;
#endif
}
bool VulkanLoad(std::string *errorStr) {
#if PPSSPP_PLATFORM(IOS_APP_STORE)
INFO_LOG(G3D, "iOS: Vulkan doesn't need loading");
return true;
#else
if (!vulkanLibrary) {
vulkanLibrary = VulkanLoadLibrary(errorStr);
if (!vulkanLibrary) {
@@ -585,9 +605,11 @@ bool VulkanLoad(std::string *errorStr) {
VulkanFreeLibrary(vulkanLibrary);
return false;
}
#endif
}
void VulkanLoadInstanceFunctions(VkInstance instance, const VulkanExtensions &enabledExtensions, uint32_t vulkanApiVersion) {
#if !PPSSPP_PLATFORM(IOS_APP_STORE)
// OK, let's use the above functions to get the rest.
LOAD_INSTANCE_FUNC(instance, vkDestroyInstance);
LOAD_INSTANCE_FUNC(instance, vkEnumeratePhysicalDevices);
@@ -655,12 +677,14 @@ void VulkanLoadInstanceFunctions(VkInstance instance, const VulkanExtensions &en
}
INFO_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 vulkanApiVersion) {
#if !PPSSPP_PLATFORM(IOS_APP_STORE)
INFO_LOG(G3D, "Vulkan device functions loaded.");
LOAD_DEVICE_FUNC(device, vkQueueSubmit);
@@ -799,10 +823,13 @@ void VulkanLoadDeviceFunctions(VkDevice device, const VulkanExtensions &enabledE
if (enabledExtensions.KHR_create_renderpass2) {
LOAD_DEVICE_FUNC_CORE(device, vkCreateRenderPass2, vkCreateRenderPass2KHR, VK_API_VERSION_1_2);
}
#endif
}
void VulkanFree() {
#if !PPSSPP_PLATFORM(IOS_APP_STORE)
VulkanFreeLibrary(vulkanLibrary);
#endif
}
const char *VulkanResultToString(VkResult res) {
+8 -1
View File
@@ -17,7 +17,9 @@
#pragma once
#ifdef __ANDROID__
#include "ppsspp_config.h"
#if PPSSPP_PLATFORM(ANDROID)
#define VK_USE_PLATFORM_ANDROID_KHR
#elif defined(_WIN32)
#define VK_USE_PLATFORM_WIN32_KHR
@@ -29,7 +31,10 @@
#define VK_USE_PLATFORM_METAL_EXT
#endif
#if !PPSSPP_PLATFORM(IOS_APP_STORE)
#define VK_NO_PROTOTYPES
#define VK_ENABLE_BETA_EXTENSIONS 1 // VK_KHR_portability_subset
#endif
#include "ext/vulkan/vulkan.h"
#include <string>
@@ -40,6 +45,7 @@
#endif
namespace PPSSPP_VK {
#if !PPSSPP_PLATFORM(IOS_APP_STORE)
// 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;
@@ -240,6 +246,7 @@ extern PFN_vkGetMemoryHostPointerPropertiesEXT vkGetMemoryHostPointerPropertiesE
extern PFN_vkWaitForPresentKHR vkWaitForPresentKHR;
extern PFN_vkGetPastPresentationTimingGOOGLE vkGetPastPresentationTimingGOOGLE;
extern PFN_vkGetRefreshCycleDurationGOOGLE vkGetRefreshCycleDurationGOOGLE;
#endif // !PPSSPP_PLATFORM(IOS_APP_STORE)
} // namespace PPSSPP_VK
// For fast extension-enabled checks.
@@ -563,6 +563,7 @@ void VulkanRenderManager::RenderThreadFunc() {
void VulkanRenderManager::PresentWaitThreadFunc() {
SetCurrentThreadName("PresentWait");
#if !PPSSPP_PLATFORM(IOS_APP_STORE)
_dbg_assert_(vkWaitForPresentKHR != nullptr);
uint64_t waitedId = frameIdGen_;
@@ -579,6 +580,7 @@ void VulkanRenderManager::PresentWaitThreadFunc() {
}
_dbg_assert_(waitedId <= frameIdGen_);
}
#endif
INFO_LOG(G3D, "Leaving PresentWaitThreadFunc()");
}
+1 -1
View File
@@ -1502,7 +1502,7 @@ void NativeShutdown() {
ShutdownWebServer();
#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS)
#if PPSSPP_PLATFORM(ANDROID)
System_ExitApp();
#endif
+8 -7
View File
@@ -112,6 +112,7 @@ enum class EmuThreadState {
STOPPED,
};
// OpenGL emu thread
static std::thread emuThread;
static std::atomic<int> emuThreadState((int)EmuThreadState::DISABLED);
@@ -1549,7 +1550,7 @@ static void ProcessFrameCommands(JNIEnv *env) {
}
}
std::thread g_vulkanRenderLoopThread;
std::thread g_renderLoopThread;
static void VulkanEmuThread(ANativeWindow *wnd);
@@ -1563,7 +1564,7 @@ extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runVulkanRenderLoo
return false;
}
if (g_vulkanRenderLoopThread.joinable()) {
if (g_renderLoopThread.joinable()) {
ERROR_LOG(G3D, "runVulkanRenderLoop: Already running");
return false;
}
@@ -1577,7 +1578,7 @@ extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runVulkanRenderLoo
return false;
}
g_vulkanRenderLoopThread = std::thread(VulkanEmuThread, wnd);
g_renderLoopThread = std::thread(VulkanEmuThread, wnd);
return true;
}
@@ -1586,11 +1587,11 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeActivity_requestExitVulkanR
ERROR_LOG(SYSTEM, "Render loop already exited");
return;
}
_assert_(g_vulkanRenderLoopThread.joinable());
_assert_(g_renderLoopThread.joinable());
exitRenderLoop = true;
g_vulkanRenderLoopThread.join();
_assert_(!g_vulkanRenderLoopThread.joinable());
g_vulkanRenderLoopThread = std::thread();
g_renderLoopThread.join();
_assert_(!g_renderLoopThread.joinable());
g_renderLoopThread = std::thread();
}
// TODO: Merge with the Win32 EmuThread and so on, and the Java EmuThread?
+3
View File
@@ -11,4 +11,7 @@
@property (strong, nonatomic) id<PPSSPPViewController> viewController;
- (void)restart:(const char *)args;
- (BOOL)launchPPSSPP:(int)argc argv:(char**)argv;
@end
+50 -12
View File
@@ -1,5 +1,7 @@
#import "AppDelegate.h"
#import "ViewControllerCommon.h"
#import "ViewController.h"
#import "ViewControllerMetal.h"
#import "iOSCoreAudio.h"
#import "Common/System/System.h"
#import "Common/System/NativeApp.h"
@@ -74,10 +76,9 @@
}
-(BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
int argc = 1;
char *argv[3] = { 0 };
NSURL* nsUrl = [launchOptions objectForKey:UIApplicationLaunchOptionsURLKey];
char *argv[5]{};
NSURL *nsUrl = [launchOptions objectForKey:UIApplicationLaunchOptionsURLKey];
if (nsUrl != nullptr && nsUrl.isFileURL) {
NSString *nsString = nsUrl.path;
@@ -85,31 +86,66 @@
argv[argc++] = (char*)string;
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioSessionInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMediaServicesWereReset:) name:AVAudioSessionMediaServicesWereResetNotification object:nil];
return [self launchPPSSPP:argc argv:argv];
}
- (BOOL)launchPPSSPP:(int)argc argv:(char**)argv;
{
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *bundlePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/assets/"];
NativeInit(argc, (const char**)argv, documentsPath.UTF8String, bundlePath.UTF8String, NULL);
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
PPSSPPViewControllerGL *vc = [[PPSSPPViewControllerGL alloc] init];
// Here we can switch viewcontroller depending on backend.
self.viewController = vc;
// Choose viewcontroller depending on backend.
if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {
PPSSPPViewControllerMetal *vc = [[PPSSPPViewControllerMetal alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioSessionInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
self.viewController = vc;
self.window.rootViewController = vc;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMediaServicesWereReset:) name:AVAudioSessionMediaServicesWereResetNotification object:nil];
} else {
PPSSPPViewControllerGL *vc = [[PPSSPPViewControllerGL alloc] init];
// Here we can switch viewcontroller depending on backend.
self.viewController = vc;
self.window.rootViewController = vc;
}
self.window.rootViewController = vc;
[self.window makeKeyAndVisible];
return YES;
}
-(void) dealloc {
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
-(void) applicationWillResignActive:(UIApplication *)application {
- (void)restart:(const char*)restartArgs {
INFO_LOG(G3D, "Restart requested: %s", restartArgs);
[self.viewController willResignActive];
[self.viewController shutdown];
self.window.rootViewController = nil;
self.viewController = nil;
// App was requested to restart, probably.
INFO_LOG(G3D, "viewController nilled");
NativeShutdown();
// TODO: Ignoring the command line for now.
// Hoping that overwriting the viewController works as expected...
[self launchPPSSPP:0 argv:nullptr];
[self.viewController didBecomeActive];
}
- (void)applicationWillResignActive:(UIApplication *)application {
INFO_LOG(G3D, "willResignActive");
[self.viewController willResignActive];
if (g_Config.bEnableSound) {
iOSCoreAudioShutdown();
}
@@ -117,12 +153,14 @@
System_PostUIMessage(UIMessage::LOST_FOCUS);
}
-(void) applicationDidBecomeActive:(UIApplication *)application {
- (void)applicationDidBecomeActive:(UIApplication *)application {
INFO_LOG(G3D, "didBecomeActive");
if (g_Config.bEnableSound) {
iOSCoreAudioInit();
}
System_PostUIMessage(UIMessage::GOT_FOCUS);
[self.viewController didBecomeActive];
}
- (void)applicationWillTerminate:(UIApplication *)application {
+5 -12
View File
@@ -133,18 +133,11 @@
}
- (void)updateResolution:(UIScreen *)screen {
float scale = screen.scale;
if ([screen respondsToSelector:@selector(nativeScale)]) {
scale = screen.nativeScale;
}
float scale = screen.nativeScale;
CGSize size = screen.bounds.size;
if (size.height > size.width) {
float h = size.height;
size.height = size.width;
size.width = h;
std::swap(size.height, size.width);
}
if (screen == [UIScreen mainScreen]) {
@@ -159,10 +152,10 @@
g_display.dpi_scale_real_y = g_display.dpi_scale_y;
g_display.pixel_xres = size.width * scale;
g_display.pixel_yres = size.height * scale;
g_display.dp_xres = g_display.pixel_xres * g_display.dpi_scale_x;
g_display.dp_yres = g_display.pixel_yres * g_display.dpi_scale_y;
g_display.pixel_in_dps_x = (float)g_display.pixel_xres / (float)g_display.dp_xres;
g_display.pixel_in_dps_y = (float)g_display.pixel_yres / (float)g_display.dp_yres;
@@ -171,7 +164,7 @@
// PSP native resize
PSP_CoreParameter().pixelWidth = g_display.pixel_xres;
PSP_CoreParameter().pixelHeight = g_display.pixel_yres;
NativeResized();
NSLog(@"Updated display resolution: (%d, %d) @%.1fx", g_display.pixel_xres, g_display.pixel_yres, scale);
-29
View File
@@ -25,35 +25,6 @@
<rect key="frame" x="0.0" y="0.0" width="667" height="375"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="icon_regular_72.png" translatesAutoresizingMaskIntoConstraints="NO" id="e6G-DH-0Lx">
<rect key="frame" x="223" y="138" width="61" height="54"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView>
<imageView opaque="NO" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="logo.png" translatesAutoresizingMaskIntoConstraints="NO" id="hGU-tP-kg8">
<rect key="frame" x="277" y="138" width="161" height="59"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Created by Henrik Rydgård" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JZS-ZP-R5c">
<rect key="frame" x="240" y="200" width="305" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Roboto-Condensed" family="Roboto" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="www.ppsspp.org" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="RXK-j5-QUl">
<rect key="frame" x="276" y="258" width="215" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Roboto-Condensed" family="Roboto" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Free software under GPL 2.0+" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CAF-LG-3vU">
<rect key="frame" x="228" y="216" width="317" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Roboto-Condensed" family="Roboto" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="highlightedColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</label>
</subviews>
<color key="backgroundColor" red="0.10634460300207138" green="0.25284945964813232" blue="0.3650897741317749" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
Submodule
+1
Submodule ios/MoltenVK added at ad018e8664.
+8 -2
View File
@@ -1,5 +1,11 @@
iOS Build Instructions
======================
The Old iOS Build Instructions
==============================
Important Notes
---------------
These instructions have not been updated for a long time and may be outdated.
Prerequisites:
--------------
+1 -2
View File
@@ -3,6 +3,7 @@
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
#import <GameController/GameController.h>
#import "iCade/iCadeReaderView.h"
#import "CameraHelper.h"
#import "LocationHelper.h"
@@ -13,5 +14,3 @@
iCadeEventDelegate, LocationHandlerDelegate, CameraFrameDelegate,
UIGestureRecognizerDelegate, UIKeyInput, PPSSPPViewController>
@end
extern id <PPSSPPViewController> sharedViewController;
+94 -68
View File
@@ -26,6 +26,7 @@
#include "Common/System/OSD.h"
#include "Common/System/NativeApp.h"
#include "Common/File/VFS/VFS.h"
#include "Common/Thread/ThreadUtil.h"
#include "Common/Log.h"
#include "Common/TimeUtil.h"
#include "Common/Input/InputState.h"
@@ -39,13 +40,6 @@
#include "Core/HLE/sceUsbCam.h"
#include "Core/HLE/sceUsbGps.h"
#include <sys/types.h>
#include <sys/sysctl.h>
#include <mach/machine.h>
#define IS_IPAD() ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
#define IS_IPHONE() ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone)
class IOSGLESContext : public GraphicsContext {
public:
IOSGLESContext() {
@@ -88,20 +82,19 @@ private:
GLRenderManager *renderManager_;
};
static bool threadEnabled = true;
static bool threadStopped = false;
static std::atomic<bool> exitRenderLoop;
static std::atomic<bool> renderLoopRunning;
static std::thread g_renderLoopThread;
id<PPSSPPViewController> sharedViewController;
// TODO: Reach these through sharedViewController
static CameraHelper *cameraHelper;
@interface PPSSPPViewControllerGL () {
ICadeTracker g_iCadeTracker;
TouchTracker g_touchTracker;
GraphicsContext *graphicsContext;
IOSGLESContext *graphicsContext;
LocationHelper *locationHelper;
CameraHelper *cameraHelper;
}
@property (nonatomic, strong) EAGLContext* context;
@@ -120,12 +113,8 @@ static CameraHelper *cameraHelper;
g_iCadeTracker.InitKeyMap();
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillTerminate:) name:UIApplicationWillTerminateNotification object:nil];
if ([GCController class]) // Checking the availability of a GameController framework
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidConnect:) name:GCControllerDidConnectNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidDisconnect:) name:GCControllerDidDisconnectNotification object:nil];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidConnect:) name:GCControllerDidConnectNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidDisconnect:) name:GCControllerDidDisconnectNotification object:nil];
}
return self;
}
@@ -158,6 +147,59 @@ extern float g_safeInsetBottom;
}
}
// The actual rendering is NOT on this thread, this is the emu thread
// that runs game logic.
void GLRenderLoop(IOSGLESContext *graphicsContext) {
SetCurrentThreadName("EmuThreadGL");
renderLoopRunning = true;
// graphicsContext->StartThread();
NativeInitGraphics(graphicsContext);
INFO_LOG(SYSTEM, "Emulation thread starting\n");
while (!exitRenderLoop) {
NativeFrame(graphicsContext);
}
INFO_LOG(SYSTEM, "Emulation thread shutting down\n");
NativeShutdownGraphics();
// Also ask the main thread to stop, so it doesn't hang waiting for a new frame.
INFO_LOG(SYSTEM, "Emulation thread stopping\n");
exitRenderLoop = false;
renderLoopRunning = false;
}
- (bool)runGLRenderLoop {
if (!graphicsContext) {
ERROR_LOG(G3D, "runVulkanRenderLoop: Tried to enter without a created graphics context.");
return false;
}
if (g_renderLoopThread.joinable()) {
ERROR_LOG(G3D, "runVulkanRenderLoop: Already running");
return false;
}
_dbg_assert_(!renderLoopRunning);
_dbg_assert_(!exitRenderLoop);
g_renderLoopThread = std::thread(GLRenderLoop, graphicsContext);
return true;
}
- (void)requestExitGLRenderLoop {
if (!renderLoopRunning) {
ERROR_LOG(SYSTEM, "Render loop already exited");
return;
}
_assert_(g_renderLoopThread.joinable());
exitRenderLoop = true;
g_renderLoopThread.join();
_assert_(!g_renderLoopThread.joinable());
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self hideKeyboard];
@@ -176,10 +218,6 @@ extern float g_safeInsetBottom;
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
}
UIScreenEdgePanGestureRecognizer *mBackGestureRecognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeFrom:) ];
[mBackGestureRecognizer setEdges:UIRectEdgeLeft];
[[self view] addGestureRecognizer:mBackGestureRecognizer];
GLKView* view = (GLKView *)self.view;
view.context = self.context;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
@@ -202,10 +240,8 @@ extern float g_safeInsetBottom;
self.iCadeView.delegate = self;
self.iCadeView.active = YES;*/
if ([GCController class]) {
if ([[GCController controllers] count] > 0) {
[self setupController:[[GCController controllers] firstObject]];
}
if ([[GCController controllers] count] > 0) {
[self setupController:[[GCController controllers] firstObject]];
}
cameraHelper = [[CameraHelper alloc] init];
@@ -216,23 +252,11 @@ extern float g_safeInsetBottom;
[self hideKeyboard];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NativeInitGraphics(graphicsContext);
UIScreenEdgePanGestureRecognizer *mBackGestureRecognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeFrom:) ];
[mBackGestureRecognizer setEdges:UIRectEdgeLeft];
[[self view] addGestureRecognizer:mBackGestureRecognizer];
INFO_LOG(SYSTEM, "Emulation thread starting\n");
while (threadEnabled) {
NativeFrame(graphicsContext);
}
INFO_LOG(SYSTEM, "Emulation thread shutting down\n");
NativeShutdownGraphics();
// Also ask the main thread to stop, so it doesn't hang waiting for a new frame.
INFO_LOG(SYSTEM, "Emulation thread stopping\n");
graphicsContext->StopThread();
threadStopped = true;
});
INFO_LOG(G3D, "Done with viewDidLoad. Next up, OpenGL");
}
- (void)handleSwipeFrom:(UIScreenEdgePanGestureRecognizer *)recognizer
@@ -252,23 +276,24 @@ extern float g_safeInsetBottom;
[self shutdown];
}
- (void)didBecomeActive {
INFO_LOG(SYSTEM, "didBecomeActive begin");
[self runGLRenderLoop];
INFO_LOG(SYSTEM, "didBecomeActive end");
}
- (void)willResignActive {
INFO_LOG(SYSTEM, "willResignActive begin");
[self requestExitGLRenderLoop];
INFO_LOG(SYSTEM, "willResignActive end");
}
- (void)shutdown
{
if (sharedViewController == nil) {
return;
}
iOSCoreAudioShutdown();
if (threadEnabled) {
threadEnabled = false;
while (graphicsContext->ThreadFrame()) {
continue;
}
while (!threadStopped) {}
graphicsContext->ThreadEnd();
}
INFO_LOG(SYSTEM, "shutdown GL");
_dbg_assert_(graphicsContext);
_dbg_assert_(sharedViewController != nil);
sharedViewController = nil;
if (self.context) {
@@ -280,22 +305,17 @@ extern float g_safeInsetBottom;
[[NSNotificationCenter defaultCenter] removeObserver:self];
if ([GCController class]) {
self.gameController = nil;
}
self.gameController = nil;
if (graphicsContext) {
graphicsContext->Shutdown();
delete graphicsContext;
graphicsContext = NULL;
}
NativeShutdown();
graphicsContext->StopThread();
graphicsContext->Shutdown();
delete graphicsContext;
graphicsContext = nullptr;
}
- (void)dealloc
{
[self shutdown];
INFO_LOG(SYSTEM, "dealloc GL");
}
- (NSUInteger)supportedInterfaceOrientations
@@ -305,8 +325,14 @@ extern float g_safeInsetBottom;
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
if (!renderLoopRunning) {
INFO_LOG(G3D, "Ignoring drawInRect");
return;
}
INFO_LOG(G3D, "drawInRect start");
if (sharedViewController)
graphicsContext->ThreadFrame();
INFO_LOG(G3D, "drawInRect end");
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
+9
View File
@@ -16,4 +16,13 @@
- (void)startVideo:(int)width height:(int)height;
- (void)stopVideo;
// Forwarded from the AppDelegate
- (void)didBecomeActive;
- (void)willResignActive;
@end
extern id <PPSSPPViewController> sharedViewController;
#define IS_IPAD() ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
#define IS_IPHONE() ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone)
+18
View File
@@ -0,0 +1,18 @@
// ViewControllerMetal
// Used by both Vulkan/MoltenVK and the future Metal backend.
#pragma once
#include "ViewControllerCommon.h"
#import "iCade/iCadeReaderView.h"
#import "CameraHelper.h"
#import "LocationHelper.h"
@interface PPSSPPViewControllerMetal : UIViewController<
iCadeEventDelegate, LocationHandlerDelegate, CameraFrameDelegate,
UIGestureRecognizerDelegate, UIKeyInput, PPSSPPViewController>
@end
/** The Metal-compatibile view. */
@interface PPSSPPMetalView : UIView
@end
+647
View File
@@ -0,0 +1,647 @@
#import "AppDelegate.h"
#import "ViewControllerMetal.h"
#import "DisplayManager.h"
#include "Controls.h"
#import "iOSCoreAudio.h"
#include "Common/Log.h"
#include "Common/GPU/Vulkan/VulkanLoader.h"
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/GPU/Vulkan/VulkanRenderManager.h"
#include "Common/GPU/thin3d.h"
#include "Common/GPU/thin3d_create.h"
#include "Common/Data/Text/Parsers.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/System/Display.h"
#include "Common/System/System.h"
#include "Common/System/OSD.h"
#include "Common/System/NativeApp.h"
#include "Common/GraphicsContext.h"
#include "Common/Thread/ThreadUtil.h"
#include "Core/Config.h"
#include "Core/ConfigValues.h"
#include "Core/System.h"
#include "Core/HLE/sceUsbCam.h"
#include "Core/HLE/sceUsbGps.h"
// ViewController lifecycle:
// https://www.progressconcepts.com/blog/ios-appdelegate-viewcontroller-method-order/
// TODO: Share this between backends.
static uint32_t FlagsFromConfig() {
uint32_t flags;
if (g_Config.bVSync) {
flags = VULKAN_FLAG_PRESENT_FIFO;
} else {
flags = VULKAN_FLAG_PRESENT_MAILBOX | VULKAN_FLAG_PRESENT_IMMEDIATE;
}
return flags;
}
enum class GraphicsContextState {
PENDING,
INITIALIZED,
FAILED_INIT,
SHUTDOWN,
};
class IOSVulkanContext : public GraphicsContext {
public:
IOSVulkanContext();
~IOSVulkanContext() {
delete g_Vulkan;
g_Vulkan = nullptr;
}
bool InitAPI();
bool InitFromRenderThread(CAMetalLayer *layer, int desiredBackbufferSizeX, int desiredBackbufferSizeY);
void ShutdownFromRenderThread(); // Inverses InitFromRenderThread.
void Shutdown();
void Resize();
void *GetAPIContext() { return g_Vulkan; }
Draw::DrawContext *GetDrawContext() { return draw_; }
private:
VulkanContext *g_Vulkan = nullptr;
Draw::DrawContext *draw_ = nullptr;
GraphicsContextState state_ = GraphicsContextState::PENDING;
};
IOSVulkanContext::IOSVulkanContext() {}
bool IOSVulkanContext::InitFromRenderThread(CAMetalLayer *layer, int desiredBackbufferSizeX, int desiredBackbufferSizeY) {
INFO_LOG(G3D, "IOSVulkanContext::InitFromRenderThread: desiredwidth=%d desiredheight=%d", desiredBackbufferSizeX, desiredBackbufferSizeY);
if (!g_Vulkan) {
ERROR_LOG(G3D, "IOSVulkanContext::InitFromRenderThread: No Vulkan context");
return false;
}
VkResult res = g_Vulkan->InitSurface(WINDOWSYSTEM_METAL_EXT, (void *)layer, nullptr);
if (res != VK_SUCCESS) {
ERROR_LOG(G3D, "g_Vulkan->InitSurface failed: '%s'", VulkanResultToString(res));
return false;
}
bool success = true;
if (g_Vulkan->InitSwapchain()) {
bool useMultiThreading = g_Config.bRenderMultiThreading;
if (g_Config.iInflightFrames == 1) {
useMultiThreading = false;
}
draw_ = Draw::T3DCreateVulkanContext(g_Vulkan, useMultiThreading);
SetGPUBackend(GPUBackend::VULKAN);
success = draw_->CreatePresets(); // Doesn't fail, we ship the compiler.
_assert_msg_(success, "Failed to compile preset shaders");
draw_->HandleEvent(Draw::Event::GOT_BACKBUFFER, g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight());
VulkanRenderManager *renderManager = (VulkanRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
renderManager->SetInflightFrames(g_Config.iInflightFrames);
success = renderManager->HasBackbuffers();
} else {
success = false;
}
INFO_LOG(G3D, "IOSVulkanContext::Init completed, %s", success ? "successfully" : "but failed");
if (!success) {
g_Vulkan->DestroySwapchain();
g_Vulkan->DestroySurface();
g_Vulkan->DestroyDevice();
g_Vulkan->DestroyInstance();
}
return success;
}
void IOSVulkanContext::ShutdownFromRenderThread() {
INFO_LOG(G3D, "IOSVulkanContext::Shutdown");
draw_->HandleEvent(Draw::Event::LOST_BACKBUFFER, g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight());
delete draw_;
draw_ = nullptr;
g_Vulkan->WaitUntilQueueIdle();
g_Vulkan->PerformPendingDeletes();
g_Vulkan->DestroySwapchain();
g_Vulkan->DestroySurface();
INFO_LOG(G3D, "Done with ShutdownFromRenderThread");
}
void IOSVulkanContext::Shutdown() {
INFO_LOG(G3D, "Calling NativeShutdownGraphics");
g_Vulkan->DestroyDevice();
g_Vulkan->DestroyInstance();
// We keep the g_Vulkan context around to avoid invalidating a ton of pointers around the app.
finalize_glslang();
INFO_LOG(G3D, "IOSVulkanContext::Shutdown completed");
}
void IOSVulkanContext::Resize() {
INFO_LOG(G3D, "IOSVulkanContext::Resize begin (oldsize: %dx%d)", g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight());
draw_->HandleEvent(Draw::Event::LOST_BACKBUFFER, g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight());
g_Vulkan->DestroySwapchain();
g_Vulkan->DestroySurface();
g_Vulkan->UpdateFlags(FlagsFromConfig());
g_Vulkan->ReinitSurface();
g_Vulkan->InitSwapchain();
draw_->HandleEvent(Draw::Event::GOT_BACKBUFFER, g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight());
INFO_LOG(G3D, "IOSVulkanContext::Resize end (final size: %dx%d)", g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight());
}
bool IOSVulkanContext::InitAPI() {
INFO_LOG(G3D, "IOSVulkanContext::Init");
init_glslang();
g_LogOptions.breakOnError = true;
g_LogOptions.breakOnWarning = true;
g_LogOptions.msgBoxOnError = false;
INFO_LOG(G3D, "Creating Vulkan context");
Version gitVer(PPSSPP_GIT_VERSION);
std::string errorStr;
if (!VulkanLoad(&errorStr)) {
ERROR_LOG(G3D, "Failed to load Vulkan driver library: %s", errorStr.c_str());
state_ = GraphicsContextState::FAILED_INIT;
return false;
}
if (!g_Vulkan) {
// TODO: Assert if g_Vulkan already exists here?
g_Vulkan = new VulkanContext();
}
VulkanContext::CreateInfo info{};
info.app_name = "PPSSPP";
info.app_ver = gitVer.ToInteger();
info.flags = FlagsFromConfig();
if (!g_Vulkan->CreateInstanceAndDevice(info)) {
delete g_Vulkan;
g_Vulkan = nullptr;
state_ = GraphicsContextState::FAILED_INIT;
return false;
}
g_Vulkan->SetCbGetDrawSize([]() {
return VkExtent2D {(uint32_t)g_display.pixel_xres, (uint32_t)g_display.pixel_yres};
});
INFO_LOG(G3D, "Vulkan device created!");
state_ = GraphicsContextState::INITIALIZED;
return true;
}
#pragma mark -
#pragma mark PPSSPPViewControllerMetal
static std::atomic<bool> exitRenderLoop;
static std::atomic<bool> renderLoopRunning;
static std::thread g_renderLoopThread;
@interface PPSSPPViewControllerMetal () {
ICadeTracker g_iCadeTracker;
TouchTracker g_touchTracker;
IOSVulkanContext *graphicsContext;
LocationHelper *locationHelper;
CameraHelper *cameraHelper;
}
@property (nonatomic) GCController *gameController __attribute__((weak_import));
@end // @interface
@implementation PPSSPPViewControllerMetal
- (id)init {
self = [super init];
if (self) {
sharedViewController = self;
g_iCadeTracker.InitKeyMap();
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillTerminate:) name:UIApplicationWillTerminateNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidConnect:) name:GCControllerDidConnectNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidDisconnect:) name:GCControllerDidDisconnectNotification object:nil];
}
return self;
}
// Should be very similar to the Android one, probably mergeable.
void VulkanRenderLoop(IOSVulkanContext *graphicsContext, CAMetalLayer *metalLayer) {
SetCurrentThreadName("EmuThreadVulkan");
INFO_LOG(G3D, "Entering EmuThreadVulkan");
if (!graphicsContext) {
ERROR_LOG(G3D, "runVulkanRenderLoop: Tried to enter without a created graphics context.");
renderLoopRunning = false;
exitRenderLoop = false;
return;
}
if (exitRenderLoop) {
WARN_LOG(G3D, "runVulkanRenderLoop: ExitRenderLoop requested at start, skipping the whole thing.");
renderLoopRunning = false;
exitRenderLoop = false;
return;
}
// This is up here to prevent race conditions, in case we pause during init.
renderLoopRunning = true;
int desiredBackbufferSizeX = g_display.pixel_xres;
int desiredBackbufferSizeY = g_display.pixel_yres;
//WARN_LOG(G3D, "runVulkanRenderLoop. desiredBackbufferSizeX=%d desiredBackbufferSizeY=%d",
// desiredBackbufferSizeX, desiredBackbufferSizeY);
if (!graphicsContext->InitFromRenderThread(metalLayer, desiredBackbufferSizeX, desiredBackbufferSizeY)) {
// On Android, if we get here, really no point in continuing.
// The UI is supposed to render on any device both on OpenGL and Vulkan. If either of those don't work
// on a device, we blacklist it. Hopefully we should have already failed in InitAPI anyway and reverted to GL back then.
ERROR_LOG(G3D, "Failed to initialize graphics context.");
System_Toast("Failed to initialize graphics context.");
delete graphicsContext;
graphicsContext = nullptr;
renderLoopRunning = false;
return;
}
if (!exitRenderLoop) {
if (!NativeInitGraphics(graphicsContext)) {
ERROR_LOG(G3D, "Failed to initialize graphics.");
// Gonna be in a weird state here..
}
graphicsContext->ThreadStart();
while (!exitRenderLoop) {
NativeFrame(graphicsContext);
}
INFO_LOG(G3D, "Leaving Vulkan main loop.");
} else {
INFO_LOG(G3D, "Not entering main loop.");
}
NativeShutdownGraphics();
graphicsContext->ThreadEnd();
// Shut the graphics context down to the same state it was in when we entered the render thread.
INFO_LOG(G3D, "Shutting down graphics context...");
graphicsContext->ShutdownFromRenderThread();
renderLoopRunning = false;
exitRenderLoop = false;
WARN_LOG(G3D, "Render loop function exited.");
}
- (bool)runVulkanRenderLoop {
INFO_LOG(G3D, "runVulkanRenderLoop");
if (!graphicsContext) {
ERROR_LOG(G3D, "runVulkanRenderLoop: Tried to enter without a created graphics context.");
return false;
}
if (g_renderLoopThread.joinable()) {
ERROR_LOG(G3D, "runVulkanRenderLoop: Already running");
return false;
}
CAMetalLayer *metalLayer = (CAMetalLayer *)self.view.layer;
g_renderLoopThread = std::thread(VulkanRenderLoop, graphicsContext, metalLayer);
return true;
}
- (void)requestExitVulkanRenderLoop {
INFO_LOG(G3D, "requestExitVulkanRenderLoop");
if (!renderLoopRunning) {
ERROR_LOG(SYSTEM, "Render loop already exited");
return;
}
_assert_(g_renderLoopThread.joinable());
exitRenderLoop = true;
g_renderLoopThread.join();
_assert_(!g_renderLoopThread.joinable());
}
// These two are forwarded from the appDelegate
- (void)didBecomeActive {
INFO_LOG(G3D, "didBecomeActive GL");
// Spin up the emu thread. It will in turn spin up the Vulkan render thread
// on its own.
[self runVulkanRenderLoop];
}
- (void)willResignActive {
INFO_LOG(G3D, "willResignActive GL");
[self requestExitVulkanRenderLoop];
}
- (void)shutdown
{
INFO_LOG(SYSTEM, "shutdown VK");
_dbg_assert_(sharedViewController != nil);
sharedViewController = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
self.gameController = nil;
if (graphicsContext) {
graphicsContext->Shutdown();
delete graphicsContext;
graphicsContext = NULL;
}
}
- (void)dealloc
{
INFO_LOG(SYSTEM, "dealloc VK");
}
- (void)loadView {
INFO_LOG(G3D, "Creating metal view");
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHeight = screenRect.size.height;
PPSSPPMetalView *metalView = [[PPSSPPMetalView alloc] initWithFrame:CGRectMake(0, 0, screenWidth,screenHeight)];
self.view = metalView;
}
- (void)viewDidLoad {
[super viewDidLoad];
[[DisplayManager shared] setupDisplayListener];
INFO_LOG(SYSTEM, "Metal viewDidLoad");
UIScreen* screen = [(AppDelegate*)[UIApplication sharedApplication].delegate screen];
self.view.frame = [screen bounds];
self.view.multipleTouchEnabled = YES;
graphicsContext = new IOSVulkanContext();
[[DisplayManager shared] updateResolution:[UIScreen mainScreen]];
if (!graphicsContext->InitAPI()) {
_assert_msg_(false, "Failed to init Vulkan");
}
if ([[GCController controllers] count] > 0) {
[self setupController:[[GCController controllers] firstObject]];
}
INFO_LOG(G3D, "Detected size: %dx%d", g_display.pixel_xres, g_display.pixel_yres);
cameraHelper = [[CameraHelper alloc] init];
[cameraHelper setDelegate:self];
locationHelper = [[LocationHelper alloc] init];
[locationHelper setDelegate:self];
[self hideKeyboard];
UIScreenEdgePanGestureRecognizer *mBackGestureRecognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeFrom:) ];
[mBackGestureRecognizer setEdges:UIRectEdgeLeft];
[[self view] addGestureRecognizer:mBackGestureRecognizer];
}
// Allow device rotation to resize the swapchain
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
// TODO: Handle resizing properly.
}
- (UIView *)getView {
return [self view];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
INFO_LOG(G3D, "viewWillAppear");
self.view.contentScaleFactor = UIScreen.mainScreen.nativeScale;
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
INFO_LOG(G3D, "viewWillDisappear");
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear: animated];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
INFO_LOG(G3D, "viewDidAppear");
[self hideKeyboard];
INFO_LOG(G3D, "viewDidAppearDone");
}
- (BOOL)prefersHomeIndicatorAutoHidden {
return YES;
}
- (void)shareText:(NSString *)text {
NSArray *items = @[text];
UIActivityViewController * viewController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:viewController animated:YES completion:nil];
});
}
extern float g_safeInsetLeft;
extern float g_safeInsetRight;
extern float g_safeInsetTop;
extern float g_safeInsetBottom;
- (void)viewSafeAreaInsetsDidChange {
if (@available(iOS 11.0, *)) {
[super viewSafeAreaInsetsDidChange];
// we use 0.0f instead of safeAreaInsets.bottom because the bottom overlay isn't disturbing (for now)
g_safeInsetLeft = self.view.safeAreaInsets.left;
g_safeInsetRight = self.view.safeAreaInsets.right;
g_safeInsetTop = self.view.safeAreaInsets.top;
g_safeInsetBottom = 0.0f;
}
}
// Enables tapping for edge area.
-(UIRectEdge)preferredScreenEdgesDeferringSystemGestures
{
return UIRectEdgeAll;
}
- (void)bindDefaultFBO
{
// Do nothing
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscape;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
g_touchTracker.Began(touches, self.view);
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
g_touchTracker.Moved(touches, self.view);
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
g_touchTracker.Ended(touches, self.view);
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
g_touchTracker.Cancelled(touches, self.view);
}
- (void)buttonDown:(iCadeState)button
{
g_iCadeTracker.ButtonDown(button);
}
- (void)buttonUp:(iCadeState)button
{
g_iCadeTracker.ButtonUp(button);
}
- (void)controllerDidConnect:(NSNotification *)note
{
if (![[GCController controllers] containsObject:self.gameController]) self.gameController = nil;
if (self.gameController != nil) return; // already have a connected controller
[self setupController:(GCController *)note.object];
}
- (void)controllerDidDisconnect:(NSNotification *)note
{
if (self.gameController == note.object) {
self.gameController = nil;
if ([[GCController controllers] count] > 0) {
[self setupController:[[GCController controllers] firstObject]];
}
}
}
- (void)setupController:(GCController *)controller
{
self.gameController = controller;
if (!SetupController(controller)) {
self.gameController = nil;
}
}
- (void)startVideo:(int)width height:(int)height {
[cameraHelper startVideo:width h:height];
}
- (void)stopVideo {
[cameraHelper stopVideo];
}
- (void)PushCameraImageIOS:(long long)len buffer:(unsigned char*)data {
Camera::pushCameraImage(len, data);
}
- (void)startLocation {
[locationHelper startLocationUpdates];
}
- (void)stopLocation {
[locationHelper stopLocationUpdates];
}
- (void)SetGpsDataIOS:(CLLocation *)newLocation {
GPS::setGpsData((long long)newLocation.timestamp.timeIntervalSince1970,
newLocation.horizontalAccuracy/5.0,
newLocation.coordinate.latitude, newLocation.coordinate.longitude,
newLocation.altitude,
MAX(newLocation.speed * 3.6, 0.0), /* m/s to km/h */
0 /* bearing */);
}
- (void)handleSwipeFrom:(UIScreenEdgePanGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateEnded) {
KeyInput key;
key.flags = KEY_DOWN | KEY_UP;
key.keyCode = NKCODE_BACK;
key.deviceId = DEVICE_ID_TOUCH;
NativeKey(key);
INFO_LOG(SYSTEM, "Detected back swipe");
}
}
// The below is inspired by https://stackoverflow.com/questions/7253477/how-to-display-the-iphone-ipad-keyboard-over-a-full-screen-opengl-es-app
// It's a bit limited but good enough.
-(void) deleteBackward {
KeyInput input{};
input.deviceId = DEVICE_ID_KEYBOARD;
input.flags = KEY_DOWN | KEY_UP;
input.keyCode = NKCODE_DEL;
NativeKey(input);
INFO_LOG(SYSTEM, "Backspace");
}
-(BOOL) hasText
{
return YES;
}
-(void) insertText:(NSString *)text
{
std::string str = std::string([text UTF8String]);
INFO_LOG(SYSTEM, "Chars: %s", str.c_str());
UTF8 chars(str);
while (!chars.end()) {
uint32_t codePoint = chars.next();
KeyInput input{};
input.deviceId = DEVICE_ID_KEYBOARD;
input.flags = KEY_CHAR;
input.unicodeChar = codePoint;
NativeKey(input);
}
}
-(BOOL) canBecomeFirstResponder
{
return YES;
}
-(void) showKeyboard {
dispatch_async(dispatch_get_main_queue(), ^{
[self becomeFirstResponder];
});
}
-(void) hideKeyboard {
dispatch_async(dispatch_get_main_queue(), ^{
[self resignFirstResponder];
});
}
@end
@implementation PPSSPPMetalView
/** Returns a Metal-compatible layer. */
+(Class) layerClass { return [CAMetalLayer class]; }
@end
+9
View File
@@ -31,6 +31,7 @@
#include "Common/System/Request.h"
#include "Common/StringUtils.h"
#include "Common/Profiler/Profiler.h"
#include "Common/Thread/ThreadUtil.h"
#include "Core/Config.h"
#include "Common/Log.h"
#include "UI/DarwinFileSystemServices.h"
@@ -391,7 +392,14 @@ void System_Notify(SystemNotification notification) {
bool System_MakeRequest(SystemRequestType type, int requestId, const std::string &param1, const std::string &param2, int64_t param3, int64_t param4) {
switch (type) {
case SystemRequestType::RESTART_APP:
dispatch_async(dispatch_get_main_queue(), ^{
[(AppDelegate *)[[UIApplication sharedApplication] delegate] restart:param1.c_str()];
});
break;
case SystemRequestType::EXIT_APP:
// NOTE: on iOS, this is considered a crash and not a valid way to exit.
exit(0);
// The below seems right, but causes hangs. See #12140.
// dispatch_async(dispatch_get_main_queue(), ^{
@@ -543,6 +551,7 @@ void System_Vibrate(int mode) {
int main(int argc, char *argv[])
{
// SetCurrentThreadName("MainThread");
version = [[[UIDevice currentDevice] systemVersion] UTF8String];
if (2 != sscanf(version.c_str(), "%d", &g_iosVersionMajor)) {
// Just set it to 14.0 if the parsing fails for whatever reason.