Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rcore] Porting raylib to iOS and implement rcore_ios.c #3880

Open
wants to merge 44 commits into
base: master
Choose a base branch
from

Conversation

blueloveTH
Copy link
Contributor

@blueloveTH blueloveTH commented Mar 22, 2024

I would like to take the initial step to add iOS support for raylib.
I am going to add rcore_ios.c and implement an iOS's platform layer.
I will update this pr on my progress.

Current Demo (iPhone 8)

RPReplay_Final1711259172.MP4

Steps

  • Create an empty iOS game project in XCode
  • Compile ANGLE and get libEGL.xcframework and libGLESv2.xcframework
  • Fill rcore_ios.c and compile raylib
  • Link all of the above with an example source file

Functions

  • void PollInputEvents(void)
  • int InitPlatform(void)
  • void ClosePlatform(void)

Prebuilt ANGLE libraries for iOS

Users need to add the following ANGLE libraries into Xcode.
libEGL.xcframework.zip
libGLESv2.xcframework.zip

References

@blueloveTH

This comment was marked as outdated.

@blueloveTH blueloveTH changed the title [rcore] Draft: Try to implement rcore_ios.c [rcore] Porting raylib into iOS and implement rcore_ios.c Mar 23, 2024
@blueloveTH blueloveTH changed the title [rcore] Porting raylib into iOS and implement rcore_ios.c [rcore] Porting raylib to iOS and implement rcore_ios.c Mar 23, 2024
@blueloveTH
Copy link
Contributor Author

blueloveTH commented Mar 23, 2024

Hi @raysan5 . I need your help. iOS always use a UIApplicationMain function to start the game. UIApplicationMain is an extern function which is hidden for me. It runs forever.

int main(int argc, char * argv[]) {
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}

It seems does not expose detailed control to allow us to achieve the following:

int main() {
  library_init();
  // game init code here
  while(we_have_not_quit_the_game) {
    library_message_loop();
    library_init_render();
    // render stuff
    library_end_render();
    // update game state
  }
  library_shutdown();
}

See https://stackoverflow.com/questions/2187684/how-can-i-remove-uiapplicationmain-from-an-iphone-application.

What we can do is registering render callbacks via CADisplayLink. I cannot find a way to give users full control of the game loop.

I can try a callback-based api like this. However, this requires a minor change of existing raylib projects if they want to run on iOS.

extern void ios_ready();
extern void ios_update();
extern void ios_destroy();

Old project should adapt their main function into this in order to be cross-platform between iOS and other platforms:

#ifndef PLATFORM_IOS
int main(int argc, char** argv){
    // store as global variables if you need
    ios_ready();
    while(!WindowShouldClose()) ios_update();
    ios_destroy();
    return 0;
}
#endif

@blueloveTH

This comment was marked as outdated.

@blueloveTH

This comment was marked as outdated.

@blueloveTH
Copy link
Contributor Author

blueloveTH commented Mar 23, 2024

Question: I see there is a feature: InitWindow(0, 0, "title") will set as full screen. How is it achieved?

Should I override CORE.Window.screen.width and CORE.Window.screen.height in InitPlatform with device width and height?

@blueloveTH blueloveTH marked this pull request as ready for review March 24, 2024 05:51
@blueloveTH
Copy link
Contributor Author

Now rcore_ios.c has basic functionalities including graphics, audio and touch input. I've marked this PR as ready for review. I will continue to work for more details and wait for raysan5's comment.

@blueloveTH

This comment was marked as outdated.

@raysan5
Copy link
Owner

raysan5 commented Apr 1, 2024

@blueloveTH Excuse my late response and thank you very much for working on this great improvement. iOS platform has been missing from raylib for +10 years and I'm happy to see that some user implemented it.

My main concern is the iOS application approach that completely differs from all the other platforms. All raylib examples work exactly as they are on all the supported platforms and requiring a complete re-design for iOS is not the ideal situation.

Is it possible to use a similar approach to Android one? Embedding the App management inside raylib?

Also note that the provided rcore_ios.c implementation does not follow raylib code conventions. It would be nice to follow them when possible.

- name: Build Xcode15 project
run: |
cd projects/Xcode15
curl -L https://github.com/raysan5/raylib/files/14743869/libEGL.xcframework.zip --output libEGL.xcframework.zip
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are those files located? It looks a bit dodgy... isn't there a better approach?

parser/output/raylib_api.lua Outdated Show resolved Hide resolved
@@ -0,0 +1 @@
Frameworks
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this .gitignore file required? Could't it be avoided?

@@ -815,7 +815,19 @@ RLAPI void rlLoadDrawQuad(void); // Load and draw a quad
#include "external/glad.h" // GLAD extensions loading library, includes OpenGL headers
#endif

#if defined(GRAPHICS_API_OPENGL_ES3)
#if defined(PLATFORM_IOS)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible I'd prefer to keep rlgl as platform-agnostic as possible, would it be possible to move this configuration to the build system or rcore.c instead of manage it here?

#error "GL_GLEXT_PROTOTYPES required on PLATFORM_IOS"
#endif
#include "libGLESv2/GLES/glext.h"
#include "libGLESv2/GLES2/gl2.h"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why GLES2 and GLES3 are included if only GLES3 is needed?

if(touchs[i] == touch) return i + 1;
}
// clear unused touch pairs before insert
for(int i = 0; i < MAX_TOUCH_POINTS; i++){
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

raylib adds one space after if, else, for and always aligns brackets.

src/platforms/rcore_ios.c Outdated Show resolved Hide resolved
src/platforms/rcore_ios.c Outdated Show resolved Hide resolved
src/platforms/rcore_ios.c Outdated Show resolved Hide resolved

/* main() */
int main(int argc, char * argv[]) {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine this is the biggest issue with iOS implementation, isn't there another alternative?

@blueloveTH
Copy link
Contributor Author

Thanks for your review. I am going to resolve them soon.

@neondeex
Copy link

neondeex commented May 2, 2024

Interesting.. will colaborate soon

@zeroxer
Copy link

zeroxer commented May 25, 2024

Any new progress?

@jonpittock
Copy link

also interested :D?

@blueloveTH
Copy link
Contributor Author

I am a bit busy recently :/ It works on my branch. I may sync it with the latest raylib if I have time.

@raysan5 raysan5 added the help needed - please! I need help with this issue label May 29, 2024
@celioreyes
Copy link

celioreyes commented Jun 13, 2024

@blueloveTH do you have local changes on your branch?

I have this running on my mac/iphone now and planning on allocating some free time on this as I also need raylib ios for a project I'm working on.

Anything you need for collaboration?

Do you have your Makefile/CMake changes to compile Raylib to ios local?

@lzralbu
Copy link
Contributor

lzralbu commented Jun 15, 2024

To use raylib with Emscripten it's already necessary to give up the main loop control(unless one's willing to add ASYNCIFY which has a nonnegligible overhead).

So I like the idea of a callback based design. Maybe something along the lines of SDL3 main callback functions.

Here's how they handle the quirks of each platform, including iOS.

@leftbones
Copy link

Definitely interested in this. This is the dream, being able to make cross platform mobile apps and games with raylib would be amazing.

@raysan5
Copy link
Owner

raysan5 commented Aug 23, 2024

@leftbones Unfortunately the code structure required for this iOS implementation is hardly compatible with raylib structure, not sure if it's possible to use another structure, more similar to Android one.

@blueloveTH
Copy link
Contributor Author

There may be internal functions that Apple does not expose allowing us to take full control of the main loop.

@jackwlee01
Copy link

Perhaps the newly merged SDL_GPU as a backend would be a better approach for targeting iOS, and consoles!!!

@calvin2021y
Copy link

@leftbones Unfortunately the code structure required for this iOS implementation is hardly compatible with raylib structure, not sure if it's possible to use another structure, more similar to Android one.

sokol seems support IOS event loop, can we learn some idea from them....

@martinfinke
Copy link

martinfinke commented Dec 3, 2024

It seems does not expose detailed control to allow us to achieve the following:

int main() {
  library_init();
  // game init code here
  while(we_have_not_quit_the_game) {
  ...
}

sokol seems support IOS event loop, can we learn some idea from them....

I'm totally new to this discussion (and raylib), but familiar with iOS and Apple APIs in general. So maybe the following totally misses the problem, but I thought I'd share it 🙂

Sokol subclasses MTKView when using Metal, or GLKView when using OpenGL ES. Then creates this view and sets the FPS. This is done inside application:didFinishLaunchingWithOptions:, and I would recommend doing it there on iOS. So in main() we still call UIApplicationMain as is. Then, iOS will call the view's drawRect: method at that FPS. This function is basically one iteration of the "game loop". So in there, we would do our updates, and call raylib's BeginDrawing() and EndDrawing() once (not writing our own blocking loop).

iOS touch events arrive in touchesBegan/Moved/Ended/Cancelled. From there, we would need to "feed them" into raylib. Maybe a queue that raylib polls every frame, when we call EndDrawing().

@martinfinke
Copy link

martinfinke commented Dec 3, 2024

I've now tested and looked over the rcore_ios.c from @blueloveTH 👍
UIApplicationMain does all kinds of setup (partial disassembly at the end of that article). Anything hacky (setjmp, 0.1 millisecond timer), while certainly clever, I don't see it viable in practice. If I use raylib for an iOS project, I'd much rather accept some entry point dictated by iOS, than something that might break with an iOS update, or get rejected later on. Every new app version has to go through App Store review again. Even if something passes once, a future app version may get rejected.

I think the only way to support iOS is to do it via UIApplicationMain and then getting called back for rendering, touch events, etc.

@raysan5
Copy link
Owner

raysan5 commented Dec 3, 2024

@martinfinke thank you very much for looking into this new iOS platform. All raylib platform backends, including Android, allow building the examples with no change for target platform.

Is it possible same behaviour for iOS?

@martinfinke
Copy link

Hi @raysan5, thank you for creating raylib!
I'm afraid I don't see a way to do the while (!WindowShouldClose()) style API on iOS. Maybe someone with more internal knowledge will come up with a way, but anything hacky will just cause Apple to reject the app.
I agree that the while (!WindowShouldClose()) is a nice style, but it's not how iOS expects it.

Just for the sake of sharing information: I came across a StackOverflow discussion, which is about GLFW on macOS (not iOS). It creates the app manually, then calls run, which would normally block until exit. However, they override applicationDidFinishLaunching:, and in there they "stop" the app, which exits the app's own blocking event loop.
So now they have the app booted up, but without blocking, which is what we're after on iOS. But the app isn't receiving events anymore, so they poll manually.

Problems with this approach on iOS: To my knowledge,

  • There's no way to stop the app's event loop, like the stop: method GLFW uses on macOS. Other than calling exit(), which terminates the entire process and looks like a crash to the user.
  • There's no API to poll events manually, like the one GLFW uses on macOS.

I also checked GLFM, but it uses the "official" UIApplicationMain approach and you implement glfmMain, but not your own main with a while loop like the raylib examples.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help needed - please! I need help with this issue
Projects
None yet
Development

Successfully merging this pull request may close these issues.