diff --git a/Common/GPU/Vulkan/VulkanFrameData.h b/Common/GPU/Vulkan/VulkanFrameData.h
index bcad1ed798..dbde815515 100644
--- a/Common/GPU/Vulkan/VulkanFrameData.h
+++ b/Common/GPU/Vulkan/VulkanFrameData.h
@@ -8,9 +8,17 @@
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/Data/Collections/Hashmaps.h"
+#if PPSSPP_PLATFORM(IOS)
+// iOS doesn't support a large number of timestamp queries natively.
+// We don't want MoltenVK to fall back to emulation.
+enum {
+ MAX_TIMESTAMP_QUERIES = 32,
+};
+#else
enum {
MAX_TIMESTAMP_QUERIES = 128,
};
+#endif
enum class VKRRunType {
SUBMIT,
diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp
index 34ee1126ec..add3fe357e 100644
--- a/Common/GPU/Vulkan/VulkanRenderManager.cpp
+++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp
@@ -423,8 +423,9 @@ void VulkanRenderManager::StartThreads() {
}
}
-// Called from main thread.
+// MUST be called from emuthread!
void VulkanRenderManager::StopThreads() {
+ INFO_LOG(Log::G3D, "VulkanRenderManager::StopThreads");
// Make sure we don't have an open non-backbuffer render pass
if (curRenderStep_ && curRenderStep_->render.framebuffer != nullptr) {
EndCurRenderStep();
diff --git a/Core/Config.cpp b/Core/Config.cpp
index dc9576cae2..ebac1e73a0 100644
--- a/Core/Config.cpp
+++ b/Core/Config.cpp
@@ -213,6 +213,14 @@ static int DefaultUIScaleFactor() {
#endif
}
+static int DefaultScreenRotation() {
+#if PPSSPP_PLATFORM(ANDROID)
+ return ROTATION_LOCKED_HORIZONTAL;
+#else
+ return ROTATION_AUTO;
+#endif
+}
+
static const ConfigSetting generalSettings[] = {
ConfigSetting("FirstRun", &g_Config.bFirstRun, true, CfgFlag::DEFAULT),
ConfigSetting("RunCount", &g_Config.iRunCount, 0, CfgFlag::DEFAULT),
@@ -297,9 +305,10 @@ static const ConfigSetting generalSettings[] = {
ConfigSetting("RemoteISOShareType", &g_Config.iRemoteISOShareType, (int)RemoteISOShareType::RECENT, CfgFlag::DEFAULT),
ConfigSetting("AskForExitConfirmationAfterSeconds", &g_Config.iAskForExitConfirmationAfterSeconds, 60, CfgFlag::PER_GAME),
-#ifdef __ANDROID__
- ConfigSetting("ScreenRotation", &g_Config.iScreenRotation, ROTATION_AUTO_HORIZONTAL),
+#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS)
+ ConfigSetting("ScreenRotation", &g_Config.iScreenRotation, &DefaultScreenRotation, CfgFlag::DEFAULT),
#endif
+
ConfigSetting("InternalScreenRotation", &g_Config.iInternalScreenRotation, ROTATION_LOCKED_HORIZONTAL, CfgFlag::PER_GAME),
ConfigSetting("BackgroundAnimation", &g_Config.iBackgroundAnimation, 1, CfgFlag::DEFAULT),
diff --git a/Core/Dialog/SavedataParam.cpp b/Core/Dialog/SavedataParam.cpp
index 4a6ac820bc..a94acc9a6e 100644
--- a/Core/Dialog/SavedataParam.cpp
+++ b/Core/Dialog/SavedataParam.cpp
@@ -200,7 +200,9 @@ void SaveFileInfo::DoState(PointerWrap &p)
delete texture;
texture = new PPGeImage("");
}
- texture->DoState(p);
+ if (texture) {
+ texture->DoState(p);
+ }
}
}
}
diff --git a/Core/ELF/ParamSFO.cpp b/Core/ELF/ParamSFO.cpp
index b92c437591..63041fc250 100644
--- a/Core/ELF/ParamSFO.cpp
+++ b/Core/ELF/ParamSFO.cpp
@@ -132,8 +132,6 @@ bool ParamSFOData::ReadSFO(const u8 *paramsfo, size_t size) {
return false;
}
- const u8 *data_start = paramsfo + header->data_table_start;
-
auto readStringCapped = [paramsfo, size](size_t offset, size_t maxLen) -> std::string {
std::string str;
while (offset < size) {
diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp
index 7d231e4b15..22417b5172 100644
--- a/Core/HLE/sceDisplay.cpp
+++ b/Core/HLE/sceDisplay.cpp
@@ -614,7 +614,7 @@ void __DisplayFlip(int cyclesLate) {
const double fpsLimit = FrameTimingLimit();
bool throttle = fpsLimit != 0.0;
- bool refreshRateNeedsSkip = fpsLimit != framerate && fpsLimit > refreshRate || !throttle;
+ bool refreshRateNeedsSkip = (fpsLimit != framerate && fpsLimit > refreshRate) || !throttle;
g_frameTiming.ComputePresentMode(draw, refreshRateNeedsSkip);
diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp
index 94035085ad..7fc46e9e4d 100644
--- a/UI/GameSettingsScreen.cpp
+++ b/UI/GameSettingsScreen.cpp
@@ -1332,19 +1332,23 @@ void GameSettingsScreen::CreateSystemSettings(UI::ViewGroup *systemSettings) {
PopupSliderChoice *exitConfirmation = systemSettings->Add(new PopupSliderChoice(&g_Config.iAskForExitConfirmationAfterSeconds, 0, 1200, 60, sy->T("Ask for exit confirmation after seconds"), screenManager(), "s"));
exitConfirmation->SetZeroLabel(sy->T("Off"));
-#if PPSSPP_PLATFORM(ANDROID)
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_MOBILE) {
auto co = GetI18NCategory(I18NCat::CONTROLS);
static const char *screenRotation[] = { "Auto", "Landscape", "Portrait", "Landscape Reversed", "Portrait Reversed", "Landscape Auto" };
PopupMultiChoice *rot = systemSettings->Add(new PopupMultiChoice(&g_Config.iScreenRotation, co->T("Screen Rotation"), screenRotation, 0, ARRAY_SIZE(screenRotation), I18NCat::CONTROLS, screenManager()));
+ #if PPSSPP_PLATFORM(IOS)
+ // Portrait Reversed is not recommended on iPhone (and we also ban it in the plist).
+ // However it's recommended to support it on iPad, so maybe we will in the future.
+ rot->HideChoice(4);
+ #endif
+
rot->OnChoice.Handle(this, &GameSettingsScreen::OnScreenRotation);
if (System_GetPropertyBool(SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE)) {
systemSettings->Add(new CheckBox(&g_Config.bSustainedPerformanceMode, sy->T("Sustained performance mode")))->OnClick.Handle(this, &GameSettingsScreen::OnSustainedPerformanceModeChange);
}
}
-#endif
systemSettings->Add(new Choice(sy->T("Restore Default Settings")))->OnClick.Handle(this, &GameSettingsScreen::OnRestoreDefaultSettings);
systemSettings->Add(new CheckBox(&g_Config.bEnableStateUndo, sy->T("Savestate slot backups")));
diff --git a/UI/ImDebugger/ImDisasmView.cpp b/UI/ImDebugger/ImDisasmView.cpp
index ac8d369d65..7d5b42acd5 100644
--- a/UI/ImDebugger/ImDisasmView.cpp
+++ b/UI/ImDebugger/ImDisasmView.cpp
@@ -52,8 +52,10 @@ static ImColor scaleColor(ImColor color, float factor) {
}
bool ImDisasmView::getDisasmAddressText(u32 address, char *dest, size_t bufSize, bool abbreviateLabels, bool showData) {
- if (PSP_GetBootState() != BootState::Complete)
+ if (PSP_GetBootState() != BootState::Complete) {
+ dest[0] = '\0';
return false;
+ }
return GetDisasmAddressText(address, dest, bufSize, abbreviateLabels, showData, displaySymbols_);
}
diff --git a/ios/AppDelegate.mm b/ios/AppDelegate.mm
index 0b9a30d0b0..94634c1f39 100644
--- a/ios/AppDelegate.mm
+++ b/ios/AppDelegate.mm
@@ -107,4 +107,24 @@ __attribute__((used)) static Class _forceLinkSceneDelegate = [SceneDelegate clas
- (void)applicationWillTerminate:(UIApplication *)application {
}
+- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
+ switch (g_Config.iScreenRotation) {
+ case ROTATION_LOCKED_HORIZONTAL:
+ return UIInterfaceOrientationMaskLandscapeRight;
+ case ROTATION_LOCKED_VERTICAL:
+ case ROTATION_LOCKED_VERTICAL180:
+ // We do not support reverse portrait on iOS.
+ return UIInterfaceOrientationMaskPortrait;
+ case ROTATION_LOCKED_HORIZONTAL180:
+ return UIInterfaceOrientationMaskLandscapeLeft;
+ case ROTATION_AUTO_HORIZONTAL:
+ return UIInterfaceOrientationMaskLandscape;
+ case ROTATION_AUTO:
+ default:
+ return UIInterfaceOrientationMaskAll;
+ }
+
+ return UIInterfaceOrientationMaskAll; // or at least include Portrait
+}
+
@end
diff --git a/ios/Controls.mm b/ios/Controls.mm
index 71d8cacca8..652a3bb9bb 100644
--- a/ios/Controls.mm
+++ b/ios/Controls.mm
@@ -86,7 +86,6 @@ bool InitController(GCController *controller) {
analogTriggerPressed(JOYSTICK_AXIS_RTRIGGER, value);
};
-#if defined(__IPHONE_12_1) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_12_1
if ([extendedProfile respondsToSelector:@selector(leftThumbstickButton)] && extendedProfile.leftThumbstickButton != nil) {
extendedProfile.leftThumbstickButton.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_BUTTON_THUMBL);
@@ -97,8 +96,7 @@ bool InitController(GCController *controller) {
controllerButtonPressed(pressed, NKCODE_BUTTON_THUMBR);
};
}
-#endif
-#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
+
if ([extendedProfile respondsToSelector:@selector(buttonOptions)] && extendedProfile.buttonOptions != nil) {
extendedProfile.buttonOptions.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_BUTTON_SELECT);
@@ -109,7 +107,7 @@ bool InitController(GCController *controller) {
controllerButtonPressed(pressed, NKCODE_BUTTON_START);
};
}
-#endif
+
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
if ([extendedProfile respondsToSelector:@selector(buttonHome)] && extendedProfile.buttonHome != nil) {
extendedProfile.buttonHome.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
diff --git a/ios/DisplayManager.mm b/ios/DisplayManager.mm
index dc891dd859..a8b72cb912 100644
--- a/ios/DisplayManager.mm
+++ b/ios/DisplayManager.mm
@@ -140,10 +140,6 @@
- (void)updateResolution:(UIScreen *)screen {
float scale = screen.nativeScale;
CGSize size = screen.bounds.size;
-
- if (size.height > size.width) {
- std::swap(size.height, size.width);
- }
float dpi;
if (screen == [UIScreen mainScreen]) {
diff --git a/ios/PPSSPP-Info.plist b/ios/PPSSPP-Info.plist
index c23fc0655d..b32d595237 100644
--- a/ios/PPSSPP-Info.plist
+++ b/ios/PPSSPP-Info.plist
@@ -60,11 +60,13 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationPortrait
UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationPortrait
UILaunchImageFile
Default.png
diff --git a/ios/SceneDelegate.h b/ios/SceneDelegate.h
index dba6229282..213895595f 100644
--- a/ios/SceneDelegate.h
+++ b/ios/SceneDelegate.h
@@ -1,12 +1,12 @@
// SceneDelegate.h
#import
+#import "ViewControllerCommon.h"
@interface SceneDelegate : UIResponder
- (void)restart:(const char *)args;
@property (strong, nonatomic) UIWindow * window;
-@property (strong, nonatomic) UIViewController *viewController;
@property (strong, nonatomic) UIWindowScene *windowScene;
@end
diff --git a/ios/SceneDelegate.mm b/ios/SceneDelegate.mm
index cc722e336a..d55e59c2c0 100644
--- a/ios/SceneDelegate.mm
+++ b/ios/SceneDelegate.mm
@@ -61,14 +61,12 @@
// Choose viewcontroller depending on backend.
if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {
PPSSPPViewControllerMetal *vc = [[PPSSPPViewControllerMetal alloc] init];
-
- self.viewController = vc;
+ // sharedViewController gets initialized in the constructor.
self.window.rootViewController = vc;
} else {
PPSSPPViewControllerGL *vc = [[PPSSPPViewControllerGL alloc] init];
// Here we can switch viewcontroller depending on backend.
- self.viewController = vc;
self.window.rootViewController = vc;
}
@@ -79,12 +77,11 @@
INFO_LOG(Log::G3D, "SceneDelegate: Restart requested: %s", restartArgs);
// Notify current view controller
- [self.viewController willResignActive];
- [self.viewController shutdown];
+ [sharedViewController willResignActive];
+ [sharedViewController shutdown];
// Remove the current root view controller
self.window.rootViewController = nil;
- self.viewController = nil;
INFO_LOG(Log::G3D, "SceneDelegate: viewController nilled");
@@ -95,13 +92,13 @@
[self launchPPSSPP];
// Notify new view controller
- [self.viewController didBecomeActive];
+ [sharedViewController didBecomeActive];
}
- (void)sceneWillResignActive:(UIScene *)scene {
INFO_LOG(Log::G3D, "sceneWillResignActive");
- [self.viewController willResignActive];
+ [sharedViewController willResignActive];
if (g_Config.bEnableSound) {
iOSCoreAudioShutdown();
@@ -119,6 +116,6 @@
System_PostUIMessage(UIMessage::GOT_FOCUS);
- [self.viewController didBecomeActive];
+ [sharedViewController didBecomeActive];
}
@end
diff --git a/ios/ViewController.mm b/ios/ViewController.mm
index ad90523eb8..b6b2b40e38 100644
--- a/ios/ViewController.mm
+++ b/ios/ViewController.mm
@@ -269,8 +269,6 @@ void GLRenderLoop(IOSGLESContext *graphicsContext) {
[EAGLContext setCurrentContext:self.context];
self.preferredFramesPerSecond = 60; // NOTE: We don't yet take advantage of 120hz screens
- [[DisplayManager shared] updateResolution:[UIScreen mainScreen]];
-
graphicsContext = new IOSGLESContext();
graphicsContext->GetDrawContext()->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) {
@@ -336,6 +334,8 @@ void GLRenderLoop(IOSGLESContext *graphicsContext) {
INFO_LOG(Log::G3D, "No accelerometer available, not starting updates.");
}
[self runGLRenderLoop];
+ [[DisplayManager shared] updateResolution:[UIScreen mainScreen]];
+
INFO_LOG(Log::System, "didBecomeActive end");
}
@@ -390,9 +390,9 @@ void GLRenderLoop(IOSGLESContext *graphicsContext) {
INFO_LOG(Log::System, "dealloc GL");
}
-- (NSUInteger)supportedInterfaceOrientations
+- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
- return UIInterfaceOrientationMaskLandscape;
+ return UIInterfaceOrientationMaskAll;
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
@@ -643,6 +643,23 @@ void GLRenderLoop(IOSGLESContext *graphicsContext) {
[self hideKeyboard];
}
+- (void)viewWillTransitionToSize:(CGSize)size
+ withTransitionCoordinator:(id)coordinator {
+ [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
+
+ [self.view endEditing:YES]; // clears any input focus
+
+ [coordinator animateAlongsideTransition:^(id context) {
+ NSLog(@"Rotating to size: %@", NSStringFromCGSize(size));
+ } completion:^(id context) {
+ NSLog(@"Rotation finished");
+ // Reinitialize graphics context to match new size
+ [self requestExitGLRenderLoop];
+ [self runGLRenderLoop];
+ [[DisplayManager shared] updateResolution:[UIScreen mainScreen]];
+ }];
+}
+
@end
void bindDefaultFBO()
diff --git a/ios/ViewControllerMetal.mm b/ios/ViewControllerMetal.mm
index 7d5ab12245..f7ce3c38ef 100644
--- a/ios/ViewControllerMetal.mm
+++ b/ios/ViewControllerMetal.mm
@@ -53,7 +53,7 @@ public:
void ShutdownFromRenderThread() override; // Inverses InitFromRenderThread.
void Shutdown() override;
- void Resize() override;
+ void Resize() override {}
void *GetAPIContext() override { return g_Vulkan; }
Draw::DrawContext *GetDrawContext() override { return draw_; }
@@ -85,7 +85,11 @@ bool IOSVulkanContext::InitFromRenderThread(CAMetalLayer *layer, int desiredBack
draw_ = Draw::T3DCreateVulkanContext(g_Vulkan, useMultiThreading);
VkPresentModeKHR presentMode = ConfigPresentModeToVulkan(draw_);
- if (!g_Vulkan->InitSwapchain(presentMode)) {
+
+ // This MUST run on the main thread. We're taking our chances with a dispatch_sync here.
+ g_Vulkan->InitSwapchain(presentMode);
+
+ if (false) {
delete draw_;
ERROR_LOG(Log::G3D, "InitSwapchain failed");
g_Vulkan->DestroySwapchain();
@@ -126,19 +130,6 @@ void IOSVulkanContext::Shutdown() {
INFO_LOG(Log::G3D, "IOSVulkanContext::Shutdown completed");
}
-void IOSVulkanContext::Resize() {
- INFO_LOG(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->ReinitSurface();
- VkPresentModeKHR presentMode = ConfigPresentModeToVulkan(draw_);
- g_Vulkan->InitSwapchain(presentMode);
- draw_->HandleEvent(Draw::Event::GOT_BACKBUFFER, g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight());
- INFO_LOG(Log::G3D, "IOSVulkanContext::Resize end (final size: %dx%d)", g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight());
-}
-
bool IOSVulkanContext::InitAPI() {
INFO_LOG(Log::G3D, "IOSVulkanContext::Init");
init_glslang();
@@ -232,6 +223,7 @@ static std::thread g_renderLoopThread;
}
// Should be very similar to the Android one, probably mergeable.
+// This is the EmuThread for iOS.
void VulkanRenderLoop(IOSVulkanContext *graphicsContext, CAMetalLayer *metalLayer) {
SetCurrentThreadName("EmuThreadVulkan");
INFO_LOG(Log::G3D, "Entering EmuThreadVulkan");
@@ -351,6 +343,7 @@ void VulkanRenderLoop(IOSVulkanContext *graphicsContext, CAMetalLayer *metalLaye
// Spin up the emu thread. It will in turn spin up the Vulkan render thread
// on its own.
[self runVulkanRenderLoop];
+ [[DisplayManager shared] updateResolution:[UIScreen mainScreen]];
}
- (void)willResignActive {
@@ -435,12 +428,6 @@ void VulkanRenderLoop(IOSVulkanContext *graphicsContext, CAMetalLayer *metalLaye
self.motionManager = [[CMMotionManager alloc] init];
}
-// 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];
}
@@ -560,11 +547,6 @@ extern float g_safeInsetBottom;
// Do nothing
}
-- (NSUInteger)supportedInterfaceOrientations
-{
- return UIInterfaceOrientationMaskLandscape;
-}
-
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
g_touchTracker.Began(touches, self.view);
@@ -763,6 +745,23 @@ extern float g_safeInsetBottom;
[self hideKeyboard];
}
+- (void)viewWillTransitionToSize:(CGSize)size
+ withTransitionCoordinator:(id)coordinator {
+ [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
+
+ [self.view endEditing:YES]; // clears any input focus
+
+ [coordinator animateAlongsideTransition:^(id context) {
+ NSLog(@"Rotating to size: %@", NSStringFromCGSize(size));
+ } completion:^(id context) {
+ NSLog(@"Rotation finished");
+ // Reinitialize graphics context to match new size
+ [self requestExitVulkanRenderLoop];
+ [self runVulkanRenderLoop];
+ [[DisplayManager shared] updateResolution:[UIScreen mainScreen]];
+ }];
+}
+
@end
@implementation PPSSPPMetalView
diff --git a/ios/main.mm b/ios/main.mm
index 47597ccaf4..bfc64a3034 100644
--- a/ios/main.mm
+++ b/ios/main.mm
@@ -431,6 +431,14 @@ void System_Notify(SystemNotification notification) {
iOSCoreAudioUpdateSession();
});
break;
+ case SystemNotification::ROTATE_UPDATED:
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (sharedViewController) {
+ // [sharedViewController setNeedsUpdateOfSupportedInterfaceOrientations];
+ INFO_LOG(Log::System, "Requesting device orientation update");
+ [UIViewController attemptRotationToDeviceOrientation];
+ }
+ });
default:
break;
}
@@ -630,9 +638,7 @@ AudioBackend *System_CreateAudioBackend() {
return nullptr;
}
-int main(int argc, char *argv[])
-{
- // SetCurrentThreadName("MainThread");
+int main(int argc, char *argv[]) {
version = [[[UIDevice currentDevice] systemVersion] UTF8String];
if (1 != sscanf(version.c_str(), "%d", &g_iosVersionMajor)) {
// Just set it to 14.0 if the parsing fails for whatever reason.