mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-12 17:19:28 +01:00
Merge pull request #19166 from hrydgard/ios-vulkan-work
Vulkan on iOS using MoltenVK
This commit is contained in:
21 files changed
+927
-146
No files matched your search
@@ -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
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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_();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -1502,7 +1502,7 @@ void NativeShutdown() {
|
||||
|
||||
ShutdownWebServer();
|
||||
|
||||
#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS)
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
System_ExitApp();
|
||||
#endif
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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:
|
||||
--------------
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ¶m1, const std::string ¶m2, 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.
|
||||
|
||||
Reference in new issue
Block a user