diff --git a/CMakeLists.txt b/CMakeLists.txt index bbe6042166..f478d8166b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1349,6 +1349,7 @@ elseif(IOS AND NOT LIBRETRO) ios/Controls.h ios/Controls.mm ios/ViewControllerCommon.h + ios/ViewControllerCommon.mm ios/ViewController.mm ios/ViewController.h ios/ViewControllerMetal.mm diff --git a/Common/UI/Context.cpp b/Common/UI/Context.cpp index f049795d35..8d532b6481 100644 --- a/Common/UI/Context.cpp +++ b/Common/UI/Context.cpp @@ -145,8 +145,6 @@ Bounds UIContext::GetLayoutBounds() const { float top = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_TOP); float bottom = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_BOTTOM); - // ILOG("Insets: %f %f %f %f", left, right, top, bottom); - // Adjust left edge to compensate for cutouts (notches) if any. bounds.x += left; bounds.w -= (left + right); diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp index ecd558703c..face815634 100644 --- a/Core/WebServer.cpp +++ b/Core/WebServer.cpp @@ -546,7 +546,7 @@ enum class MultiPartResult { static MultiPartResult HandleMultipartPart(const http::ServerRequest &request, std::string boundary, const Path &uploadPath, ProgressTracker &progress) { std::string firstBoundary = request.In()->ReadLine(); if (firstBoundary != "--" + boundary) { - WARN_LOG(Log::HTTP, "Bad boundary: Expected --%s but got %s", boundary.c_str()); + WARN_LOG(Log::HTTP, "Bad boundary: Expected --%s but got %s", boundary.c_str(), firstBoundary.c_str()); return MultiPartResult::RequestError; } diff --git a/UI/DebugOverlay.cpp b/UI/DebugOverlay.cpp index 0a9e4386f2..19b64472a1 100644 --- a/UI/DebugOverlay.cpp +++ b/UI/DebugOverlay.cpp @@ -495,8 +495,8 @@ void DrawFPS(UIContext *ctx, const Bounds &bounds) { ctx->BindFontTexture(); ctx->Draw()->SetFontScale(0.7f, 0.7f); - ctx->Draw()->DrawText(ubuntu24, w.as_view(), bounds.x2() - 8, 20, 0xc0000000, ALIGN_TOPRIGHT | FLAG_DYNAMIC_ASCII); - ctx->Draw()->DrawText(ubuntu24, w.as_view(), bounds.x2() - 10, 19, 0xFF3fFF3f, ALIGN_TOPRIGHT | FLAG_DYNAMIC_ASCII); + ctx->Draw()->DrawText(ubuntu24, w.as_view(), bounds.x2() - 8, bounds.y + 10, 0xc0000000, ALIGN_TOPRIGHT | FLAG_DYNAMIC_ASCII); + ctx->Draw()->DrawText(ubuntu24, w.as_view(), bounds.x2() - 10, bounds.y + 8, 0xFF3fFF3f, ALIGN_TOPRIGHT | FLAG_DYNAMIC_ASCII); ctx->Draw()->SetFontScale(1.0f, 1.0f); ctx->Flush(); ctx->RebindTexture(); diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 6ea5d06b27..416eb8eca7 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -1128,7 +1128,7 @@ GameBrowser *MainScreen::CreateBrowserTab(const Path &path, std::string_view tit return gameBrowser; } -UI::ViewGroup *MainScreen::CreateLogoView(UI::LayoutParams *layoutParams) { +UI::ViewGroup *MainScreen::CreateLogoView(bool portrait, UI::LayoutParams *layoutParams) { using namespace UI; AnchorLayout *logos = new AnchorLayout(layoutParams); if (System_GetPropertyBool(SYSPROP_APP_GOLD)) { @@ -1156,7 +1156,7 @@ UI::ViewGroup *MainScreen::CreateLogoView(UI::LayoutParams *layoutParams) { ver->SetClip(false); // Only allow copying the version if it looks like a git version string. 1.19 for example is not really necessary to be able to copy/paste. - if (strchr(PPSSPP_GIT_VERSION, '-')) { + if (!portrait && strchr(PPSSPP_GIT_VERSION, '-')) { ver->OnClick.Add([](UI::EventParams &e) { auto di = GetI18NCategory(I18NCat::DIALOG); System_CopyStringToClipboard(PPSSPP_GIT_VERSION); @@ -1298,7 +1298,7 @@ void MainScreen::CreateViews() { if (vertical) { LinearLayout *header = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); header->SetSpacing(5.0f); - header->Add(CreateLogoView(new LinearLayoutParams(WRAP_CONTENT, 80.0f, false))); + header->Add(CreateLogoView(true, new LinearLayoutParams(WRAP_CONTENT, 80.0f, false))); LinearLayout *buttonGroup = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 1.0f, UI::Gravity::G_VCENTER, UI::Margins(0,0,8,0))); @@ -1317,7 +1317,7 @@ void MainScreen::CreateViews() { ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins)); LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); rightColumnItems->SetSpacing(0.0f); - ViewGroup *logo = CreateLogoView(new LinearLayoutParams(FILL_PARENT, 80.0f, false)); + ViewGroup *logo = CreateLogoView(false, new LinearLayoutParams(FILL_PARENT, 80.0f)); #if !defined(MOBILE_DEVICE) auto gr = GetI18NCategory(I18NCat::GRAPHICS); ImageID icon(g_Config.UseFullScreen() ? "I_RESTORE" : "I_FULLSCREEN"); diff --git a/UI/MainScreen.h b/UI/MainScreen.h index 358c60fca2..ed918b6142 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -136,7 +136,7 @@ protected: void CreateViews() override; void CreateRecentTab(); GameBrowser *CreateBrowserTab(const Path &path, std::string_view title, std::string_view howToTitle, std::string_view howToUri, BrowseFlags browseFlags, bool *bGridView, float *scrollPos); - UI::ViewGroup *CreateLogoView(UI::LayoutParams *layoutParams); + UI::ViewGroup *CreateLogoView(bool portrait, UI::LayoutParams *layoutParams); void CreateMainButtons(UI::ViewGroup *parent, bool vertical); void DrawBackground(UIContext &dc) override; diff --git a/ios/Controls.mm b/ios/Controls.mm index 652a3bb9bb..e71215a870 100644 --- a/ios/Controls.mm +++ b/ios/Controls.mm @@ -108,13 +108,13 @@ bool InitController(GCController *controller) { }; } -#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) { - controllerButtonPressed(pressed, NKCODE_HOME); - }; + if (@available(iOS 14.0, *)) { + if ([extendedProfile respondsToSelector:@selector(buttonHome)] && extendedProfile.buttonHome != nil) { + extendedProfile.buttonHome.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_HOME); + }; + } } -#endif extendedProfile.leftThumbstick.xAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) { AxisInput axisInput; diff --git a/ios/ViewController.h b/ios/ViewController.h index ae7844a519..a9120dd758 100644 --- a/ios/ViewController.h +++ b/ios/ViewController.h @@ -10,8 +10,13 @@ #include "ViewControllerCommon.h" -@interface PPSSPPViewControllerGL : GLKViewController < +@interface PPSSPPViewControllerGL : UIViewController + + +// Public-ish control similar to GLKViewController +@property (nonatomic, assign) NSInteger preferredFramesPerSecond; // default 60 + @end diff --git a/ios/ViewController.mm b/ios/ViewController.mm index b6b2b40e38..df7c2d754f 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -3,18 +3,20 @@ // // Created by rock88 // Modified by xSacha -// +// Reworked by hrydgard #import "AppDelegate.h" #import "ViewController.h" #import "DisplayManager.h" -#include "Controls.h" #import "iOSCoreAudio.h" #import "IAPManager.h" #import -#include +#import "ViewController.h" +#import "Controls.h" +#import +#include #include "Common/Net/Resolve.h" #include "Common/UI/Screen.h" #include "Common/GPU/thin3d.h" @@ -109,9 +111,13 @@ id sharedViewController; NSString *imageFilename; } +@property (nonatomic, strong) EAGLContext *glContext; +@property (nonatomic, strong) GLKView *glView; +@property (nonatomic, strong) CADisplayLink *displayLink; +@property (nonatomic, assign) NSTimeInterval lastTimestamp; + @property (nonatomic, strong) EAGLContext* context; -//@property (nonatomic) iCadeReaderView* iCadeView; @property (nonatomic) GCController *gameController __attribute__((weak_import)); @property (strong, nonatomic) CMMotionManager *motionManager; @property (strong, nonatomic) NSOperationQueue *accelerometerQueue; @@ -122,9 +128,11 @@ id sharedViewController; UIScreenEdgePanGestureRecognizer *mBackGestureRecognizer; } + -(id) init { self = [super init]; if (self) { + _preferredFramesPerSecond = 60; // default sharedViewController = self; g_iCadeTracker.InitKeyMap(); @@ -251,23 +259,45 @@ void GLRenderLoop(IOSGLESContext *graphicsContext) { - (void)viewDidLoad { [super viewDidLoad]; + + // 1) Create GL context + self.glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; + if (!self.glContext) { + self.glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + } + NSAssert(self.glContext != nil, @"Failed to create EAGLContext"); + + // 2) Create GLKView + self.glView = [[GLKView alloc] initWithFrame:self.view.bounds context:self.glContext]; + self.glView.delegate = self; + self.glView.enableSetNeedsDisplay = NO; // We'll call display manually + self.glView.drawableDepthFormat = GLKViewDrawableDepthFormat24; + self.glView.drawableStencilFormat = GLKViewDrawableStencilFormat8; + self.glView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [self.view addSubview:self.glView]; + + // Put context current for initial GL setup + [EAGLContext setCurrentContext:self.glContext]; + + // Here we can do one time GL init if we want. + + // 3) Setup display link + self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkFired:)]; + if (@available(iOS 10.0, *)) { + self.displayLink.preferredFramesPerSecond = (NSInteger)self.preferredFramesPerSecond; + } else { + // older iOS: approximate with frameInterval + self.displayLink.frameInterval = MAX(1, (NSInteger)round(60.0 / self.preferredFramesPerSecond)); + } + [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; + + self.lastTimestamp = 0; + [[DisplayManager shared] setupDisplayListener]; UIScreen* screen = [(AppDelegate*)[UIApplication sharedApplication].delegate screen]; self.view.frame = [screen bounds]; self.view.multipleTouchEnabled = YES; - self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; - - if (!self.context) { - self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; - } - - GLKView* view = (GLKView *)self.view; - view.context = self.context; - view.drawableDepthFormat = GLKViewDrawableDepthFormat24; - view.drawableStencilFormat = GLKViewDrawableStencilFormat8; - [EAGLContext setCurrentContext:self.context]; - self.preferredFramesPerSecond = 60; // NOTE: We don't yet take advantage of 120hz screens graphicsContext = new IOSGLESContext(); @@ -299,8 +329,79 @@ void GLRenderLoop(IOSGLESContext *graphicsContext) { INFO_LOG(Log::G3D, "Done with viewDidLoad."); } -- (void)handleSwipeFrom:(UIScreenEdgePanGestureRecognizer *)recognizer -{ +- (void)viewDidLayoutSubviews { + [super viewDidLayoutSubviews]; + self.glView.frame = self.view.bounds; + /* + // if you need to update viewport: + CGSize s = self.glView.bounds.size; + [EAGLContext setCurrentContext:self.glContext]; + glViewport(0, 0, (GLsizei)round(s.width), (GLsizei)round(s.height)); + */ +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + // Resume display link unless explicitly paused + INFO_LOG(Log::G3D, "viewWillAppear - resuming display link"); +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + // stop rendering while not visible + INFO_LOG(Log::G3D, "viewWillDisappear - pausing display link"); +} + +- (void)dealloc { + [self.displayLink invalidate]; + self.displayLink = nil; + + if ([EAGLContext currentContext] == self.glContext) { + [EAGLContext setCurrentContext:nil]; + } + self.glContext = nil; +} + +- (void)setPreferredFramesPerSecond:(NSInteger)preferredFramesPerSecond { + _preferredFramesPerSecond = preferredFramesPerSecond; + if (self.displayLink) { + if (@available(iOS 10.0, *)) { + self.displayLink.preferredFramesPerSecond = (NSInteger)preferredFramesPerSecond; + } else { + self.displayLink.frameInterval = MAX(1, (NSInteger)round(60.0 / preferredFramesPerSecond)); + } + } +} + +- (void)displayLinkFired:(CADisplayLink *)dl { + // compute delta time + NSTimeInterval timestamp = dl.timestamp; + NSTimeInterval delta = 0; + if (self.lastTimestamp > 0) { + delta = timestamp - self.lastTimestamp; + } else { + delta = dl.duration; // fallback + } + self.lastTimestamp = timestamp; + + // Ensure context is current before drawing + [EAGLContext setCurrentContext:self.glContext]; + + // Trigger GLKView draw + [self.glView display]; +} + +- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect { + if (!renderLoopRunning) { + INFO_LOG(Log::G3D, "Ignoring drawInRect"); + return; + } + if (sharedViewController) { + graphicsContext->ThreadFrame(true); + } +} + +- (void)handleSwipeFrom:(UIScreenEdgePanGestureRecognizer *)recognizer { if (recognizer.state == UIGestureRecognizerStateEnded) { KeyInput key; key.flags = KEY_DOWN | KEY_UP; @@ -337,6 +438,8 @@ void GLRenderLoop(IOSGLESContext *graphicsContext) { [[DisplayManager shared] updateResolution:[UIScreen mainScreen]]; INFO_LOG(Log::System, "didBecomeActive end"); + + self.displayLink.paused = NO; } - (void)willResignActive { @@ -349,6 +452,8 @@ void GLRenderLoop(IOSGLESContext *graphicsContext) { [self.motionManager stopAccelerometerUpdates]; } INFO_LOG(Log::System, "willResignActive end"); + + self.displayLink.paused = YES; } - (void)shutdown @@ -385,27 +490,11 @@ void GLRenderLoop(IOSGLESContext *graphicsContext) { INFO_LOG(Log::System, "Done shutting down GL"); } -- (void)dealloc -{ - INFO_LOG(Log::System, "dealloc GL"); -} - - (UIInterfaceOrientationMask)supportedInterfaceOrientations { return UIInterfaceOrientationMaskAll; } -- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect -{ - if (!renderLoopRunning) { - INFO_LOG(Log::G3D, "Ignoring drawInRect"); - return; - } - if (sharedViewController) { - graphicsContext->ThreadFrame(true); - } -} - - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { g_touchTracker.Began(touches, self.view); @@ -428,7 +517,7 @@ void GLRenderLoop(IOSGLESContext *graphicsContext) { - (void)bindDefaultFBO { - [(GLKView*)self.view bindDrawable]; + [(GLKView*)self.glView bindDrawable]; } - (void)buttonDown:(iCadeState)button diff --git a/ios/ViewControllerCommon.mm b/ios/ViewControllerCommon.mm new file mode 100644 index 0000000000..ff611ab217 --- /dev/null +++ b/ios/ViewControllerCommon.mm @@ -0,0 +1,2 @@ +#import "ios/ViewControllerCommon.h" + diff --git a/ios/ViewControllerMetal.mm b/ios/ViewControllerMetal.mm index f7ce3c38ef..85877f75e6 100644 --- a/ios/ViewControllerMetal.mm +++ b/ios/ViewControllerMetal.mm @@ -491,13 +491,25 @@ extern float g_safeInsetRight; extern float g_safeInsetTop; extern float g_safeInsetBottom; +static float BoostInset(float inset) { + if (inset > 0.0f) { + // If there's some inset, add a few pixels extra. Really needed on iPhone 12, at least. + inset += 4.0f; + } + return inset; +} + - (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_safeInsetLeft = BoostInset(self.view.safeAreaInsets.left); + g_safeInsetRight = BoostInset(self.view.safeAreaInsets.right); + g_safeInsetTop = BoostInset(self.view.safeAreaInsets.top); + + // TODO: In portrait mode, should probably use safeAreaInsets.bottom. + // However, in landscape mode, it's not really needed. + // g_safeInsetBottom = BoostInset(self.view.safeAreaInsets.bottom); g_safeInsetBottom = 0.0f; } }