// // ViewController.m // // Created by rock88 // Modified by xSacha // Reworked by hrydgard #import "AppDelegate.h" #import "ViewController.h" #import "iOSCoreAudio.h" #import #import #include #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/GLFeatures.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/GraphicsContext.h" #include "Core/Config.h" #include "Core/ConfigValues.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 class IOSGLESContext : public GraphicsContext { public: IOSGLESContext() { CheckGLExtensions(); draw_ = Draw::T3DCreateGLContext(false); renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); renderManager_->SetInflightFrames(g_Config.iInflightFrames); SetGPUBackend(GPUBackend::OPENGL); bool success = draw_->CreatePresets(); _assert_msg_(success, "Failed to compile preset shaders"); } ~IOSGLESContext() { delete draw_; } Draw::DrawContext *GetDrawContext() override { return draw_; } void Resize() override {} void Shutdown() override {} void BeginShutdown() { renderManager_->SetSkipGLCalls(); } void ThreadStart() override { renderManager_->ThreadStart(draw_); } bool ThreadFrame(bool waitIfEmpty) override { return renderManager_->ThreadFrame(waitIfEmpty); } void ThreadEnd() override { renderManager_->ThreadEnd(); } void StartThread() { renderManager_->StartThread(); } private: Draw::DrawContext *draw_; GLRenderManager *renderManager_; }; static std::atomic exitRenderLoop; static std::atomic renderLoopRunning; static std::thread g_renderLoopThread; PPSSPPBaseViewController *sharedViewController; @interface PPSSPPViewControllerGL () { IOSGLESContext *graphicsContext; int imageRequestId; 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; @end @implementation PPSSPPViewControllerGL {} -(id) init { self = [super init]; if (self) { _preferredFramesPerSecond = 60; // default } return self; } // 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; NativeInitGraphics(graphicsContext); INFO_LOG(Log::System, "Emulation thread starting\n"); while (!exitRenderLoop) { NativeFrame(graphicsContext); } INFO_LOG(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(Log::System, "Emulation thread stopping\n"); exitRenderLoop = false; renderLoopRunning = false; } - (bool)runGLRenderLoop { if (!graphicsContext) { ERROR_LOG(Log::G3D, "runVulkanRenderLoop: Tried to enter without a created graphics context."); return false; } if (g_renderLoopThread.joinable()) { ERROR_LOG(Log::G3D, "runVulkanRenderLoop: Already running"); return false; } _dbg_assert_(!renderLoopRunning); _dbg_assert_(!exitRenderLoop); graphicsContext->StartThread(); g_renderLoopThread = std::thread(GLRenderLoop, graphicsContext); return true; } - (void)requestExitGLRenderLoop { if (!renderLoopRunning) { ERROR_LOG(Log::System, "Render loop already exited"); return; } _assert_(g_renderLoopThread.joinable()); exitRenderLoop = true; graphicsContext->ThreadFrameUntilCondition([]() -> bool { return !renderLoopRunning.load(); }); g_renderLoopThread.join(); _assert_(!g_renderLoopThread.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.lastTimestamp = 0; UIScreen* screen = [(AppDelegate*)[UIApplication sharedApplication].delegate screen]; self.view.frame = [screen bounds]; self.view.multipleTouchEnabled = YES; graphicsContext = new IOSGLESContext(); graphicsContext->GetDrawContext()->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) { g_OSD.Show(OSDType::MESSAGE_ERROR, details, 0.0f, "error_callback"); }, nullptr); graphicsContext->ThreadStart(); /*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]; // Initialize the motion manager for accelerometer control. INFO_LOG(Log::G3D, "Done with viewDidLoad."); } - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; self.glView.frame = self.view.bounds; } - (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)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); if (self.context) { if ([EAGLContext currentContext] == self.context) { [EAGLContext setCurrentContext:nil]; } self.context = nil; } [[NSNotificationCenter defaultCenter] removeObserver:self]; graphicsContext->BeginShutdown(); // Skipping GL calls here because the old context is lost. graphicsContext->ThreadFrameUntilCondition([]() -> bool { return !renderLoopRunning; }); graphicsContext->ThreadEnd(); graphicsContext->Shutdown(); delete graphicsContext; graphicsContext = nullptr; INFO_LOG(Log::System, "Done shutting down GL"); } - (void)bindDefaultFBO { [(GLKView*)self.glView bindDrawable]; } - (UIView *)getView { return [self view]; } // Can't consolidate this yet. - (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 updateResolutionWithView:self.view]; [self runGLRenderLoop]; }]; } @end void bindDefaultFBO() { [sharedViewController bindDefaultFBO]; } void EnableFZ(){}; void DisableFZ(){};