mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-17 08:20:30 +02:00
Both view controllers stopped the emu/render thread on every rotation
and started it again, tearing down all of graphics. Since d0e45b94d9
NativeFrame resizes the graphics context and posts GPU_DISPLAY_RESIZED
on iOS too, so a NativeResized() is all a size change needs, like
Android now does. GLKView resizes its drawable by itself, and the Vulkan
context recreates the swapchain, which MoltenVK sizes from the layer.
The resize happens in viewDidLayoutSubviews, which UIKit runs at the
final size as a rotation animation starts. Resizing when the rotation
completed instead left half the screen black while it animated. This
also covers window resizes, which matter more now that
UIRequiresFullScreen is deprecated (iPadOS 26).
NativeFrame sets the PSP_CoreParameter() size, so the main thread
doesn't have to.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
271 lines
6.9 KiB
Plaintext
271 lines
6.9 KiB
Plaintext
//
|
|
// ViewController.m
|
|
//
|
|
// Created by rock88
|
|
// Modified by xSacha
|
|
// Reworked by hrydgard
|
|
|
|
#import "ViewController.h"
|
|
#import "iOSCoreAudio.h"
|
|
|
|
#import <GLKit/GLKit.h>
|
|
#import <QuartzCore/QuartzCore.h>
|
|
|
|
#include <cassert>
|
|
#include "Common/Net/Resolve.h"
|
|
#include "Common/UI/Screen.h"
|
|
#include "Common/GPU/thin3d.h"
|
|
#include "Common/GPU/thin3d_create.h"
|
|
#include "Common/GPU/OpenGL/GLRenderManager.h"
|
|
#include "Common/GPU/OpenGL/OpenGLGraphicsContext.h"
|
|
#include "Common/System/Display.h"
|
|
#include "Common/System/System.h"
|
|
#include "Common/System/OSD.h"
|
|
#include "Common/System/NativeApp.h"
|
|
#include "Common/System/Request.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"
|
|
#include "Common/Input/KeyCodes.h"
|
|
#include "Common/GPU/GraphicsContext.h"
|
|
|
|
#include "Core/Config.h"
|
|
#include "Core/ConfigValues.h"
|
|
#include "Core/EmuThread.h"
|
|
#include "Core/KeyMap.h"
|
|
#include "Core/System.h"
|
|
|
|
#if !__has_feature(objc_arc)
|
|
#error Must be built with ARC, please revise the flags for ViewController.mm to include -fobjc-arc.
|
|
#endif
|
|
|
|
static std::thread g_emuThread;
|
|
|
|
PPSSPPBaseViewController *sharedViewController;
|
|
|
|
@interface PPSSPPViewControllerGL () {
|
|
GraphicsContext *graphicsContext;
|
|
}
|
|
|
|
@property (nonatomic, strong) EAGLContext *glContext;
|
|
@property (nonatomic, strong) GLKView *glView;
|
|
@property (nonatomic, strong) CADisplayLink *displayLink;
|
|
|
|
@end
|
|
|
|
@implementation PPSSPPViewControllerGL {}
|
|
|
|
-(id) init {
|
|
self = [super init];
|
|
if (self) {
|
|
_preferredFramesPerSecond = 60; // default
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (bool)runGLRenderLoop {
|
|
if (!graphicsContext) {
|
|
ERROR_LOG(Log::G3D, "runGLRenderLoop: Tried to enter without a created graphics context.");
|
|
return false;
|
|
}
|
|
|
|
if (g_emuThread.joinable()) {
|
|
ERROR_LOG(Log::G3D, "runGLRenderLoop: Already running");
|
|
return false;
|
|
}
|
|
|
|
g_emuThread = EmuThread_Start(graphicsContext, new NativeApplication(), [](GraphicsContext *graphicsContext){
|
|
NativeFrame(graphicsContext);
|
|
return true;
|
|
});
|
|
return true;
|
|
}
|
|
|
|
- (void)requestExitGLRenderLoop {
|
|
if (!g_emuThread.joinable()) {
|
|
INFO_LOG(Log::G3D, "requestExitGLRenderLoop: Not running");
|
|
return;
|
|
}
|
|
EmuThread_Join(graphicsContext, g_emuThread);
|
|
_assert_(!g_emuThread.joinable());
|
|
}
|
|
|
|
- (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.glView.multipleTouchEnabled = YES;
|
|
[self.view addSubview:self.glView];
|
|
|
|
// self.view.insetsLayoutMarginsFromSafeArea = NO;
|
|
// self.view.clipsToBounds = YES;
|
|
|
|
// 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.view.multipleTouchEnabled = YES;
|
|
|
|
graphicsContext = new OpenGLGraphicsContext();
|
|
|
|
std::string errorMessage;
|
|
if (!graphicsContext->InitAPI(nullptr, nullptr, &errorMessage)) {
|
|
ERROR_LOG(Log::G3D, "InitAPI failed: %s", errorMessage.c_str());
|
|
}
|
|
|
|
if (!graphicsContext->InitSurface(WINDOWSYSTEM_NONE, nullptr, nullptr, &errorMessage)) {
|
|
ERROR_LOG(Log::G3D, "InitSurface failed: %s", errorMessage.c_str());
|
|
}
|
|
|
|
/*self.iCadeView = [[iCadeReaderView alloc] init];
|
|
[self.view addSubview:self.iCadeView];
|
|
self.iCadeView.delegate = self;
|
|
self.iCadeView.active = YES;*/
|
|
|
|
if ([[GCController controllers] count] > 0) {
|
|
[self setupController:[[GCController controllers] firstObject]];
|
|
}
|
|
|
|
[self hideKeyboard];
|
|
|
|
INFO_LOG(Log::G3D, "Done with viewDidLoad.");
|
|
}
|
|
|
|
- (void)viewDidLayoutSubviews {
|
|
[super viewDidLayoutSubviews];
|
|
self.glView.frame = self.view.bounds;
|
|
}
|
|
|
|
- (void)viewWillAppear:(BOOL)animated {
|
|
[super viewWillAppear:animated];
|
|
INFO_LOG(Log::G3D, "viewWillAppear");
|
|
}
|
|
|
|
- (void)viewWillDisappear:(BOOL)animated {
|
|
[super viewWillDisappear:animated];
|
|
INFO_LOG(Log::G3D, "viewWillDisappear");
|
|
}
|
|
|
|
- (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 {
|
|
// Ensure context is current before drawing
|
|
[EAGLContext setCurrentContext:self.glContext];
|
|
|
|
// Trigger GLKView draw
|
|
[self.glView display];
|
|
}
|
|
|
|
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
|
|
if (!g_emuThread.joinable()) {
|
|
INFO_LOG(Log::G3D, "Ignoring drawInRect");
|
|
return;
|
|
}
|
|
if (sharedViewController) {
|
|
if (!graphicsContext->ThreadFrame()) {
|
|
INFO_LOG(Log::G3D, "ThreadFrame returned false");
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)didBecomeActive {
|
|
[super didBecomeActive];
|
|
|
|
INFO_LOG(Log::System, "didBecomeActive begin");
|
|
|
|
[self updateResolutionWithView:self.view];
|
|
[self runGLRenderLoop];
|
|
|
|
INFO_LOG(Log::System, "didBecomeActive end");
|
|
|
|
self.displayLink.paused = NO;
|
|
}
|
|
|
|
- (void)willResignActive {
|
|
INFO_LOG(Log::System, "willResignActive GL");
|
|
[self requestExitGLRenderLoop];
|
|
|
|
self.displayLink.paused = YES;
|
|
|
|
[super willResignActive];
|
|
}
|
|
|
|
- (void)shutdown {
|
|
[super shutdown];
|
|
|
|
INFO_LOG(Log::System, "shutdown GL");
|
|
|
|
g_Config.Save("shutdown GL");
|
|
|
|
_dbg_assert_(graphicsContext);
|
|
|
|
// Normally already done by willResignActive, but not when the app is terminated.
|
|
[self requestExitGLRenderLoop];
|
|
|
|
graphicsContext->ShutdownSurface();
|
|
graphicsContext->ShutdownAPI();
|
|
delete graphicsContext;
|
|
graphicsContext = nullptr;
|
|
INFO_LOG(Log::System, "Done shutting down GL");
|
|
}
|
|
|
|
- (void)bindDefaultFBO
|
|
{
|
|
[(GLKView*)self.glView bindDrawable];
|
|
}
|
|
|
|
@end
|
|
|
|
void bindDefaultFBO()
|
|
{
|
|
[sharedViewController bindDefaultFBO];
|
|
}
|
|
|
|
void EnableFZ(){};
|
|
void DisableFZ(){};
|