feat: add iOS threading and vsync handling

This commit is contained in:
vsaint1 2026-05-27 02:48:14 -03:00
parent eba14aadbc
commit 202a35833e

View File

@ -1,6 +1,8 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <OpenGLES/EAGL.h> #import <OpenGLES/EAGL.h>
#import <OpenGLES/ES3/gl.h> #import <OpenGLES/ES3/gl.h>
#import <QuartzCore/CADisplayLink.h>
#include <QuartzCore/CAFrameRateRange.h>
#import <QuartzCore/QuartzCore.h> #import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@ -41,6 +43,11 @@ typedef struct IOSBridgeState {
static IOSBridgeState ios = {0}; 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 @interface RaylibGLView : UIView
@end @end
@ -75,6 +82,7 @@ static IOSBridgeState ios = {0};
[super viewWillDisappear:animated]; [super viewWillDisappear:animated];
ios_set_window_focused(false); ios_set_window_focused(false);
} }
- (void)forwardTouches:(NSSet<UITouch *> *)touches action:(int)action { - (void)forwardTouches:(NSSet<UITouch *> *)touches action:(int)action {
for (UITouch *touch in touches) { for (UITouch *touch in touches) {
CGPoint point = [touch locationInView:self.view]; CGPoint point = [touch locationInView:self.view];
@ -122,57 +130,113 @@ static IOSBridgeState ios = {0};
@end @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) if (!ios.initialized || ios.shuttingDown)
return; return;
ios.shuttingDown = true; 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]) if ([NSThread isMainThread])
shutdownBlock(); shutdown_internal();
else else
dispatch_sync(dispatch_get_main_queue(), shutdownBlock); dispatch_async(dispatch_get_main_queue(), ^{
shutdown_internal();
ios.shuttingDown = false; });
} }
@interface RaylibAppDelegate : UIResponder <UIApplicationDelegate> @interface RaylibAppDelegate : UIResponder <UIApplicationDelegate>
@end @end
@interface RaylibSceneDelegate : UIResponder <UIWindowSceneDelegate> @interface RaylibSceneDelegate : UIResponder <UIWindowSceneDelegate>
@property(strong, nonatomic) UIWindow *window; @property(strong, nonatomic) UIWindow *window;
@end @end
static void ios_start_raylib_main(void) { static void ios_start_raylib_main(void) {
if (ios.raylibStarted) if (ios.raylibStarted)
return; return;
ios.raylibStarted = true; 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); raylib_main(ios.argc, ios.argv);
}); });
} }
@ -240,6 +304,8 @@ static void ios_start_raylib_main(void) {
self.window.rootViewController = ios.viewController; self.window.rootViewController = ios.viewController;
[self.window makeKeyAndVisible]; [self.window makeKeyAndVisible];
[RaylibDisplayLinkTarget startDisplayLink];
ios_start_raylib_main(); ios_start_raylib_main();
} }
@ -256,7 +322,7 @@ static void ios_start_raylib_main(void) {
- (void)sceneDidDisconnect:(UIScene *)scene { - (void)sceneDidDisconnect:(UIScene *)scene {
(void)scene; (void)scene;
ios_request_close(); ios_request_close();
ios_shutdown_window(); ios_close_platform();
} }
@end @end
@ -314,9 +380,6 @@ static BOOL ios_setup_gl(void) {
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER, ios.depthBuffer); GL_RENDERBUFFER, ios.depthBuffer);
BOOL fbComplete =
(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
GLenum fbStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER); GLenum fbStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (fbStatus != GL_FRAMEBUFFER_COMPLETE) { if (fbStatus != GL_FRAMEBUFFER_COMPLETE) {
NSLog(@"[raylib][IOS] Framebuffer incomplete: 0x%04X", fbStatus); NSLog(@"[raylib][IOS] Framebuffer incomplete: 0x%04X", fbStatus);
@ -327,7 +390,7 @@ static BOOL ios_setup_gl(void) {
[EAGLContext setCurrentContext:nil]; [EAGLContext setCurrentContext:nil];
return fbComplete; return fbStatus == GL_FRAMEBUFFER_COMPLETE;
} }
bool ios_initialize_window(int requestedWidth, int requestedHeight, bool ios_initialize_window(int requestedWidth, int requestedHeight,
@ -419,6 +482,17 @@ void ios_present_frame(void) {
err = glGetError(); err = glGetError();
if (err != GL_NO_ERROR) if (err != GL_NO_ERROR)
NSLog(@"[raylib][IOS] glGetError after present: 0x%04X", err); 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; } 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; *scaleX = ios.scaleX;
if (scaleY) if (scaleY)
*scaleY = ios.scaleY; *scaleY = ios.scaleY;
} }
void *ios_get_proc_address(const char *name) { void *ios_get_proc_address(const char *name) {