From 202a35833eb83de60ecb9c297555560f139d48a9 Mon Sep 17 00:00:00 2001 From: vsaint1 Date: Wed, 27 May 2026 02:48:14 -0300 Subject: [PATCH] feat: add iOS threading and vsync handling --- src/rcore_ios_main.m | 148 ++++++++++++++++++++++++++++++++----------- 1 file changed, 111 insertions(+), 37 deletions(-) diff --git a/src/rcore_ios_main.m b/src/rcore_ios_main.m index 51624badf..ffb188cb4 100644 --- a/src/rcore_ios_main.m +++ b/src/rcore_ios_main.m @@ -1,6 +1,8 @@ #import #import #import +#import +#include #import #import @@ -41,6 +43,11 @@ typedef struct IOSBridgeState { static IOSBridgeState ios = {0}; +static dispatch_queue_t raylibQueue = nil; +static CADisplayLink *displayLink = nil; +static dispatch_semaphore_t frameSem = nil; +static int64_t vsyncCount = 0; + @interface RaylibGLView : UIView @end @@ -75,6 +82,7 @@ static IOSBridgeState ios = {0}; [super viewWillDisappear:animated]; ios_set_window_focused(false); } + - (void)forwardTouches:(NSSet *)touches action:(int)action { for (UITouch *touch in touches) { CGPoint point = [touch locationInView:self.view]; @@ -122,57 +130,113 @@ static IOSBridgeState ios = {0}; @end -void ios_shutdown_window(void) { +@interface RaylibDisplayLinkTarget : NSObject ++ (void)startDisplayLink; ++ (void)onDisplayFrame:(CADisplayLink *)link; ++ (void)stopDisplayLink; +@end + +@implementation RaylibDisplayLinkTarget + ++ (void)startDisplayLink { + if (displayLink != nil) + return; + + frameSem = dispatch_semaphore_create(0); + vsyncCount = 0; + + displayLink = + [CADisplayLink displayLinkWithTarget:[self class] + selector:@selector(onDisplayFrame:)]; + + NSInteger maxFPS = [UIScreen mainScreen].maximumFramesPerSecond; + + // Use the display native refresh rate + displayLink.preferredFrameRateRange = + CAFrameRateRangeMake(60.0f, maxFPS, maxFPS); + + [displayLink addToRunLoop:[NSRunLoop mainRunLoop] + forMode:NSRunLoopCommonModes]; +} + ++ (void)onDisplayFrame:(CADisplayLink *)link { + (void)link; + __atomic_fetch_add(&vsyncCount, 1, __ATOMIC_RELEASE); + dispatch_semaphore_signal(frameSem); +} + ++ (void)stopDisplayLink { + if (displayLink == nil) + return; + + [displayLink invalidate]; + displayLink = nil; + + dispatch_semaphore_signal(frameSem); +} + +@end + +static void shutdown_internal(void) { + [RaylibDisplayLinkTarget stopDisplayLink]; + + if (ios.context != nil) { + [EAGLContext setCurrentContext:ios.context]; + + if (ios.framebuffer != 0) + glDeleteFramebuffers(1, &ios.framebuffer); + if (ios.colorBuffer != 0) + glDeleteRenderbuffers(1, &ios.colorBuffer); + if (ios.depthBuffer != 0) + glDeleteRenderbuffers(1, &ios.depthBuffer); + + ios.framebuffer = 0; + ios.colorBuffer = 0; + ios.depthBuffer = 0; + ios.context = nil; + } + + ios.glView = nil; + ios.viewController = nil; + ios.window = nil; + ios.initialized = false; +} + +void ios_close_platform(void) { if (!ios.initialized || ios.shuttingDown) return; + ios.shuttingDown = true; - void (^shutdownBlock)(void) = ^{ - if (ios.context != nil) { - [EAGLContext setCurrentContext:ios.context]; - - if (ios.framebuffer != 0) - glDeleteFramebuffers(1, &ios.framebuffer); - if (ios.colorBuffer != 0) - glDeleteRenderbuffers(1, &ios.colorBuffer); - if (ios.depthBuffer != 0) - glDeleteRenderbuffers(1, &ios.depthBuffer); - - ios.framebuffer = 0; - ios.colorBuffer = 0; - ios.depthBuffer = 0; - ios.context = nil; - } - - ios.glView = nil; - ios.viewController = nil; - ios.window = nil; - ios.initialized = false; - }; - if ([NSThread isMainThread]) - shutdownBlock(); + shutdown_internal(); else - dispatch_sync(dispatch_get_main_queue(), shutdownBlock); - - ios.shuttingDown = false; + dispatch_async(dispatch_get_main_queue(), ^{ + shutdown_internal(); + }); } @interface RaylibAppDelegate : UIResponder @end @interface RaylibSceneDelegate : UIResponder - @property(strong, nonatomic) UIWindow *window; - @end static void ios_start_raylib_main(void) { if (ios.raylibStarted) return; + ios.raylibStarted = true; - dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{ + raylibQueue = + dispatch_queue_create("com.raylib.engine", DISPATCH_QUEUE_SERIAL); + + dispatch_async(raylibQueue, ^{ + [[NSThread currentThread] setName:@"com.raylib.engine"]; + + // NSLog(@"[raylib][IOS] raylib thread: %@", [NSThread currentThread].name); + raylib_main(ios.argc, ios.argv); }); } @@ -240,6 +304,8 @@ static void ios_start_raylib_main(void) { self.window.rootViewController = ios.viewController; [self.window makeKeyAndVisible]; + [RaylibDisplayLinkTarget startDisplayLink]; + ios_start_raylib_main(); } @@ -256,7 +322,7 @@ static void ios_start_raylib_main(void) { - (void)sceneDidDisconnect:(UIScene *)scene { (void)scene; ios_request_close(); - ios_shutdown_window(); + ios_close_platform(); } @end @@ -314,9 +380,6 @@ static BOOL ios_setup_gl(void) { glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, ios.depthBuffer); - BOOL fbComplete = - (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); - GLenum fbStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (fbStatus != GL_FRAMEBUFFER_COMPLETE) { NSLog(@"[raylib][IOS] Framebuffer incomplete: 0x%04X", fbStatus); @@ -327,7 +390,7 @@ static BOOL ios_setup_gl(void) { [EAGLContext setCurrentContext:nil]; - return fbComplete; + return fbStatus == GL_FRAMEBUFFER_COMPLETE; } bool ios_initialize_window(int requestedWidth, int requestedHeight, @@ -419,6 +482,17 @@ void ios_present_frame(void) { err = glGetError(); if (err != GL_NO_ERROR) NSLog(@"[raylib][IOS] glGetError after present: 0x%04X", err); + if (frameSem != nil && !ios.shuttingDown) { + static int64_t lastVsync = 0; + int64_t current = __atomic_load_n(&vsyncCount, __ATOMIC_ACQUIRE); + + while ((current - lastVsync) < 1 && !ios.shuttingDown) { + dispatch_semaphore_wait(frameSem, DISPATCH_TIME_FOREVER); + current = __atomic_load_n(&vsyncCount, __ATOMIC_ACQUIRE); + } + + lastVsync = current; + } } void *ios_get_window_handle(void) { return (__bridge void *)ios.window; } @@ -442,7 +516,7 @@ void ios_get_window_metrics(int *screenWidth, int *screenHeight, *scaleX = ios.scaleX; if (scaleY) - *scaleY = ios.scaleY; + *scaleY = ios.scaleY; } void *ios_get_proc_address(const char *name) {