Support screen rotation on iOS (unrestricted)

This commit is contained in:
Henrik Rydgård
2025-05-27 15:50:39 +02:00
parent 9bc28cae3a
commit 1c49ad7b29
16 changed files with 65 additions and 57 deletions
+8
View File
@@ -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,
+2 -1
View File
@@ -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();
+3 -1
View File
@@ -200,7 +200,9 @@ void SaveFileInfo::DoState(PointerWrap &p)
delete texture;
texture = new PPGeImage("");
}
texture->DoState(p);
if (texture) {
texture->DoState(p);
}
}
}
}
-2
View File
@@ -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) {
+1 -1
View File
@@ -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);
+6 -2
View File
@@ -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")));
+3 -1
View File
@@ -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_);
}
+4
View File
@@ -107,4 +107,8 @@ __attribute__((used)) static Class _forceLinkSceneDelegate = [SceneDelegate clas
- (void)applicationWillTerminate:(UIApplication *)application {
}
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
return UIInterfaceOrientationMaskAll; // or at least include Portrait
}
@end
+2 -4
View File
@@ -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) {
-4
View File
@@ -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]) {
+2
View File
@@ -60,11 +60,13 @@
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UILaunchImageFile</key>
<string>Default.png</string>
+1 -1
View File
@@ -1,12 +1,12 @@
// SceneDelegate.h
#import <UIKit/UIKit.h>
#import "ViewControllerCommon.h"
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
- (void)restart:(const char *)args;
@property (strong, nonatomic) UIWindow * window;
@property (strong, nonatomic) UIViewController *viewController;
@property (strong, nonatomic) UIWindowScene *windowScene;
@end
+6 -9
View File
@@ -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
+2 -2
View File
@@ -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
+24 -26
View File
@@ -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");
@@ -435,12 +427,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 +546,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 +744,23 @@ extern float g_safeInsetBottom;
[self hideKeyboard];
}
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[self.view endEditing:YES]; // clears any input focus
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
NSLog(@"Rotating to size: %@", NSStringFromCGSize(size));
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
NSLog(@"Rotation finished");
// Reinitialize graphics context to match new size
[self requestExitVulkanRenderLoop];
[self runVulkanRenderLoop];
[[DisplayManager shared] updateResolution:[UIScreen mainScreen]];
}];
}
@end
@implementation PPSSPPMetalView
+1 -3
View File
@@ -630,9 +630,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.