[iOS] Multiple issues (and solutions/workarounds) trying to run jme3 app

I was having an exception in android (cannot remember what it was exactly , I can paste the stackTrace to you later ) when trying to add a sky map using the default code , so I have changed the depth of rendering & it works fine .

Hi @Pavl_G! I tried the exact same code on android and worked for me, so it shouldn’t be related with my exception. Sounds weird to me that changing the material render state fixed your issue. Please post the stacktrace when possible, I’m curious about that. Also, what jme3 release are you using?

1 Like

Hi @joliver82

Not related to the issue, but curious if JME audio works on iOS?

1 Like

I am curious too , because it’s not working on android , too.

I think for android, you should use jme3-android-native.jar 3.3.0-alpha2 or below. (more details here)

Edit: for android issues you may want to create a new topic. :slightly_smiling_face:

2 Likes

I’ve not tested sound on ios still but according to this post it’s supposed to be fixed either using ios-data from @revv fork or using 3.3 (which already includes the required changes).

About android, as @Ali_RS said you need to downgrade your android native

2 Likes

Hi again!

I’m trying to run a simple app on a physical device because gl support in simulator is just crap. Xcode requires the app to be signed and I set up a team linking xcode to my apple ID and I’m getting the following error:

Command CodeSign failed with a nonzero exit code

Being more specific, it’s outputting “errSecInternalComponent”

I’ve search and most people had the same issue and was fixed just by locking the login keystore and unlocking it on demand when xcode tried to sign an app, removing sign keys so xcode creates a new one and even just by rebooting. None of those worked for me

I’ve also read that something inside the app itself could make the codesign binary fail, so I tried to build a default iphone app created from scratch with the same result.

I know this is not strictly jme3 related but I appreciate any help

Thanks

EDIT:

I just found the issue is caused because apple ca expired: WWDR Intermediate Certificate Expiration - Support - Apple Developer if using xcode <=11.4.0 (my case) you need to manually download and install new WWDR certificates

Hi, sorry for late reply. Did you manage to get it working and deploy to app store?

Hi @GTWhite!

I’m still on it. My app is not finished so I’m not planning to publish it to app store yet but I wanted to check current ios support and the possibility to publish it also there, but it’s being a real pain :frowning:

Now I found other issue. If using any filter the screen rendered black without any information on xcode log.

Then I realized the ios gl wrapper had the same issue android gl had before, glTexImage2D was using format as internal format:

@Override
public void glTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer data) {
    JmeIosGLES.glTexImage2D(target, level, format, width, height, 0, format, type, data);
}

So I fixed it as follows:

@Override
public void glTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer data) {
    JmeIosGLES.glTexImage2D(target, level, internalFormat, width, height, 0, format, type, data);
}

Now I’m getting the following exception:

java/lang/IllegalStateException: Framebuffer has erronous attachment.
  at com/jme3/renderer/opengl/GLRenderer.checkFrameBufferError (line 1704)
  at com/jme3/renderer/opengl/GLRenderer.updateFrameBuffer (line 1877)
  at com/jme3/renderer/opengl/GLRenderer.setFrameBuffer (line 2018)
  at com/jme3/renderer/RenderManager.renderViewPort (line 1082)
  at com/jme3/renderer/RenderManager.render (line 1158)
  at com/jme3/app/SimpleApplication.update (line 272)
  at JmeAppHarness.appDraw (unknown line)

I thought it could be because jme3 uses RGB16F format if float textures are supported and maybe apple hardware failed on that although the extension was exposed so I tried forcing it to RGB8: fpp.setFrameBufferFormat(Image.Format.RGB8) with no luck.

I’ve tried to use the GLDebug class by enabling GraphicsDebug in AppSettings but the app just dies, so I added some manual prints at related methods.

The texture is being created as internalFormat=GL_RGB16F, format=GL_RGB and type=GL_HALF_FLOAT_OES which should be ok if the extension is available but glTexImage2D is giving error GL_INVALID_OPERATION as if the format was wrong

Have you used filters in any of your apps?

** Edited the whole post to give more details

EDIT2:

I’ve been working deeper into this, it seems that there’s a big difference between ES2 and ES3 (and also desktop GL) in glTexImage2d: glTexImage2D gles2 vs glTexImage2d gles3 in the first case it specifies “internalformat must match format. No conversion between formats is supported during texture image processing. type may be used as a hint to specify how much precision is desired, but a GL implementation may choose to store the texture array at any internal resolution it chooses.” So it was correct for iphone as it’s using gles2. Reverting my change and checking why it’s being rendered black :thinking:

Also, iOS insists on power of 2 images. Don’t know if you have come across that?

Hi, I think it’s also worth mentioning that Apple have deprecated OpenGL from iOS 12 and they are no longer including the JVM framework in Xcode 12, so you need to use Xcode 11 for now and you have to target iOS 13 or below. They want us to migrate to Metal kit instead which I am currently looking into

Also, OpenAL is deprecated on iOS

And, Avian is currently not being maintained so the template for iOS as a whole needs to be revisited. Don’t suppose you have got time on your hands?? I haven’t got much but I’m working on it.

It’s been plenty of hours spent on this during the weekend but I finally found out! iOS has some bad gl implementation and default framebuffer is not necessarily 0 but any random number (most times 1). Some references on this:

So I modified GLRenderer to take this into consideration as follows:

private defaultFBO=0;

public void initialize() {
    /* current code not shown to reduce post size */
    IntBuffer tmp=BufferUtils.createIntBuffer(16);
    gl.glGetInteger(36006, tmp); // 36006 stands for GL_FRAMEBUFFER_BINDING
    tmp.rewind();
    int fbOnLoad=tmp.get();
    if(fbOnLoad>0)
    {
        System.out.println("Overriding default FB to: " + fbOnLoad);
        this.defaultFBO=fbOnLoad;
    }
}

private void bindFrameBuffer(FrameBuffer fb) {
    if (fb == null) {
        if (context.boundFBO != defaultFBO) {
            glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, defaultFBO);
            statistics.onFrameBufferUse(null, true);
            context.boundFBO = defaultFBO;
            context.boundFB = null;
        }
    } else {
        assert fb.getId() != -1 && fb.getId() != 0;
        if (context.boundFBO != fb.getId()) {
            glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, fb.getId());
            context.boundFBO = fb.getId();
            context.boundFB = fb;
            statistics.onFrameBufferUse(fb, true);
        } else {
            statistics.onFrameBufferUse(fb, false);
        }
    }
}

This way the proper framebuffer is used and the scene with filters is properly rendered now. :v:

About non power of two textures, jme adds Caps.PartialNonPowerOfTwoTextures when rendering using GLES2.0 but I don’t know what this capability implies… Anyway, I use power of two textures 99.9% percent of the times just in case

I know apple deprecated openGL in favour of metal but I don’t think they’ll remove it anytime soon from their OSs. Or at least I hope so. In fact, as my app relies on plenty of jme3 functionality requiring gles3 I’m implementing missing gles3 stuff for iOS (currently working 90%) and I’m planning on creating a PR for it during this week, including previous fix. I know this may be a loss of time, but while gl is not removed it’s a good improvement to have…

Are you having a look into implementing a metal renderer for jme? Wow! this can be a really hard topic to address.

I’m fine with using xcode 11, I only own a hackintosh running 10.15 and I don’t have any intention to update :stuck_out_tongue:

I didn’t know openAL is also deprecated… For the long term ios will require a lot of work in jme to support this platform :frowning:

I knew about avian also. As I posted before maybe it should be replaced with a newer and supported jvm. I’m not an expert in jdk inners neither in apple and/or ios itself, but I have some spare time now and I’d be glad to help. Have you thought on any alternatives? Do you have a hub thread for this? We could talk about it there.

I confirm iOS sound is currently working, just tested it

3 Likes

Thanks for confirmation :slightly_smiling_face:

By the way, for Avian replacement, you may want to take a look at Oracle’s Graalvm native image.

And for GLES → Metal, you may want to look at

iOS support is planned yet.

Hope you find it useful.

Thanks for the comment.

I was having a look at graalvm and also to minijvm (GitHub - digitalgust/miniJVM: Develop iOS Android app in java, Cross platform java virtual machine , the minimal jvm .) but not in the mood to get into this still.

About google’s angle, sadly it still doesn’t support metal, so it’s not an option for ios nowadays but could be a good replacement as jme3 default renderer instead of having to mess ourselves with vulkan, gl and metal

Other issue here… If running a really simple app (skybox + a plain blue cube) if using iphone 11pro (ios 13.0) it’s not rendering fullscreen.

Regular rendering tested on physical iphone 5 12.4, iphone 8plus (13.3) and some other:

Bad rendering at 11pro:

Any ideas?

EDIT:

ios 13.0 has this weird behaviour, using any iphone in the simulator running 13.0 breaks rendering while 12.4 and 13.3 work as expected.

Also, on ios13.0 rotating the device fixes it as described in this post

that is to do with the scaling which is explained how to resolve in both mine and Dark Chaos’s previous posts. i’ll find it or repost the code sample shortly

So in short the screen on iOS is rendered at 1024x768 and if your device is higher res than that (which they all are) then it will show in the corner.

You have to scale it but also take into account the orientation (landscape/portrait) and the relative resolution of the device.

I’ll just give you my code to achieve this. It’s in the jmeAppDelegate and you put it in the rotate notification event.

#pragma mark - Device Actions

-(void)rotate {
[self didRotate:nil];
}

  • (void)didRotate:(NSNotification *)notification
    {

    if (self.vm != nil) {
    CGRect originalFrame = [[UIScreen mainScreen] bounds];
    CGRect frame = [_glview convertRect:originalFrame fromView:nil];

      UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
      UIDeviceOrientation dorientation = [[UIDevice currentDevice] orientation];
      
      JNIEnv* e = getEnv(self.vm);
      if (e) {
          float scale = _glview.contentScaleFactor;
          int width = (int)frame.size.width;
          int height = (int)frame.size.height;
          
          if ((dorientation == UIDeviceOrientationLandscapeRight || dorientation == UIDeviceOrientationLandscapeLeft) && (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown)) {
              
              
              (*e)->CallVoidMethod(e, self.harness, self.reshapeMethod, (int)(frame.size.height * scale), (int)(frame.size.width * scale), (int)frame.size.height, (int)frame.size.width);
              
          }
          else
          {
              (*e)->CallVoidMethod(e, self.harness, self.reshapeMethod, (int)(frame.size.width * scale), (int)(frame.size.height * scale), (int)frame.size.width, (int)frame.size.height);
              
          }
                   
          
          if ((*e)->ExceptionCheck(e)) {
              NSLog(@"Could not invoke iOS Harness reshape");
              (*e)->ExceptionDescribe(e);
              (*e)->ExceptionClear(e);
          }
      }
    

    }

}

This is my jmeHarness which includes another fix for the touch location also needs to be scaled to fit the device resolution

import com.inventiveinspiration.ChatterGames.CCHarness;
import com.inventiveinspiration.ChatterGames.Main;
import com.jme3.system.ios.IosHarness;
import com.jme3.input.ios.IosInputHandler;
import com.jme3.renderer.opengl.GLRenderer;
import com.jme3.system.JmeContext;
import com.jme3.system.AppSettings;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JmeAppHarness extends IosHarness implements CCHarness {

private float IOSScaleFactorX = 1.0f;
private float IOSScaleFactorY = 1.0f;
private int originalWidth = 1024;
private int originalHeight = 768;

private static final Logger logger = Logger.getLogger(JmeAppHarness.class.getName());
protected GLRenderer renderer;
protected IosInputHandler input;
protected boolean autoFlush = true;



public JmeAppHarness(long id) {
    super(id);
    app = new com.inventiveinspiration.ChatterGames.Main();

   
    

    AppSettings settings = new AppSettings(true);
   
    app.setSettings(settings);
   
    originalWidth = 1024;
    originalHeight = 768;


    app.start();

    logger.log(Level.FINE, "JmeAppHarness constructor");          
    app.gainFocus();

       
}

@Override
public void appPaused() {
    logger.log(Level.FINE, "JmeAppHarness appPaused");
}

@Override
public void appReactivated() {
}

@Override
public void appClosed() {
    logger.log(Level.FINE, "JmeAppHarness appClosed");
    app.stop();
}

@Override
public void appUpdate() {
    logger.log(Level.FINE, "JmeAppHarness appUpdate");
    //app.update();
}

@Override
public void appDraw() {
    logger.log(Level.FINE, "JmeAppHarness appDraw");
    if (renderer == null) {
        JmeContext iosContext = app.getContext();
        renderer = (GLRenderer) iosContext.getRenderer();
        renderer.initialize();
        input = (IosInputHandler) iosContext.getTouchInput();
        input.initialize();
    } else {
        app.update();
        if (autoFlush) {
            renderer.postFrame();
        }
    }
}

@Override
public void appReshape(int width, int height) {
    logger.log(Level.FINE, "JmeAppHarness reshape");
    AppSettings settings = app.getContext().getSettings();
    settings.setResolution(width, height);
    if (renderer != null) {
        app.reshape(width, height);
    }
    if (input != null) {
        input.loadSettings(settings);
    }
}

public void injectTouchBegin(int pointerId, long time, float x, float y) {
    if (input != null) {
        logger.log(Level.FINE, "JmeAppHarness injectTouchBegin");
        input.injectTouchDown(pointerId, time, x, y);
    }
}

public void injectTouchMove(int pointerId, long time, float x, float y) {
    if (input != null) {
        logger.log(Level.FINE, "JmeAppHarness injectTouchMove");
        input.injectTouchMove(pointerId, time, x, y);
    }
}

public void injectTouchEnd(int pointerId, long time, float x, float y) {
    if (input != null) {
        logger.log(Level.FINE, "JmeAppHarness injectTouchEnd");
        input.injectTouchUp(pointerId, time, x, y);
    }
}


@Override
public void reshapeGame(int width, int height, int origwidth, int origheight) {
    logger.log(Level.FINE, "JmeAppHarness reshape");
    AppSettings settings = app.getContext().getSettings();

    if (width < 1) {
        width = 640;
    }
    if (height < 1) {
        height = 480;
    }

    if (origwidth < 1) {
        origwidth = 640;
    }
    if (origheight < 1) {
        origheight = 480;
    }        
    

    originalWidth = origwidth;
    originalHeight = origheight;

    settings.setResolution(width, height);
    IOSScaleFactorX = (float) width / (float) originalWidth;
    IOSScaleFactorY = (float) height / (float) originalHeight;

    ((Main) app).setIOSScaleFactor(IOSScaleFactorX, IOSScaleFactorY, width, height, origwidth, origheight);

    if (renderer != null) {

        app.reshape(width, height);
    }

    if (input != null) {

        input.loadSettings(settings);

        ((Main) app).IOSInitialiseTouch(origwidth, origheight);

    }

}




public void IOSInitialiseTouch() {

    if (renderer != null) {

        ((Main) app).IOSInitialiseTouch(originalWidth, originalHeight);
    } else {
        System.out.println("Can't reset touch because Renderer is not ready....");
    }
};

}

and I have a method in my Main class which actions the resolution and touch scaling when the device is rotated.

You also have to call rotate once when the app starts or you have to rotate the device to get it scale properly.

public void IOSInitialiseTouch(int W, int H) {

    settings = getContext().getSettings();
    settings.setHeight(H);
    settings.setWidth(W);
    getContext().setSettings(settings);
    settings = getContext().getSettings();
    resetWidth = W;
    resetHeight = H;
    
}

And finally ive added a slightly amended touch events which includes the timestamp to be cast to a long as it fails otherwise. That may have been fixed since I wrote this code.

#pragma mark - GLKViewDelegate

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    JNIEnv* e = getEnv(self.vm);
    if (e) {
        (*e)->CallVoidMethod(e, self.harness, self.drawMethod);
        if ((*e)->ExceptionCheck(e)) {
            NSLog(@"Could not invoke iOS Harness update");
            (*e)->ExceptionDescribe(e);
            (*e)->ExceptionClear(e);
        }
    }
}

#pragma mark - GLKViewControllerDelegate

- (void)glkViewControllerUpdate:(GLKViewController *)controller {
    JNIEnv* e = getEnv(self.vm);
    if (e) {
        (*e)->CallVoidMethod(e, self.harness, self.updateMethod);
        if ((*e)->ExceptionCheck(e)) {
            NSLog(@"Could not invoke iOS Harness update");
            (*e)->ExceptionDescribe(e);
            (*e)->ExceptionClear(e);
        }
    }
    
}


#pragma mark - UIResponder

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"touchesBegan");
    if (self.vm == nil) {
        
    }
    else
    {
        JNIEnv* e = getEnv(self.vm);
        if (e) {
            UITouch *touch = [touches anyObject];
            CGPoint position = [touch locationInView: nil];
            (*e)->CallVoidMethod(e, self.harness, self.injectTouchBegin, 0, (jlong)touch.timestamp, position.x, position.y);
            if ((*e)->ExceptionCheck(e)) {
                NSLog(@"Could not invoke iOS Harness injectTouchBegin");
                (*e)->ExceptionDescribe(e);
                (*e)->ExceptionClear(e);
            }
        }
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"touchesMoved");
    if (self.vm == nil) {
        
    }
    else
    {
        JNIEnv* e = getEnv(self.vm);
        if (e) {
            UITouch *touch = [touches anyObject];
            CGPoint position = [touch locationInView: nil];
            (*e)->CallVoidMethod(e, self.harness, self.injectTouchMove, 0, (jlong)touch.timestamp, position.x, position.y);
            if ((*e)->ExceptionCheck(e)) {
                NSLog(@"Could not invoke iOS Harness injectTouchMove");
                (*e)->ExceptionDescribe(e);
                (*e)->ExceptionClear(e);
            }
        }
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"touchesEnded");
    if (self.vm == nil) {
        
    }
    else
    {
        JNIEnv* e = getEnv(self.vm);
        if (e) {
            
            
            UITouch *touch = [touches anyObject];
            CGPoint position = [touch locationInView: nil];
            (*e)->CallVoidMethod(e, self.harness, self.injectTouchEnd, 0, (jlong)touch.timestamp, position.x, position.y);
            if ((*e)->ExceptionCheck(e)) {
                NSLog(@"Could not invoke iOS Harness injectTouchEnd");
                (*e)->ExceptionDescribe(e);
                (*e)->ExceptionClear(e);
            }
        }
    }
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"touchesCancelled");
}

For your info these are my interface descriptors from jmeAppDelegate

jclass harnessClass = (*e)->FindClass(e, "JmeAppHarness");
if (! (*e)->ExceptionCheck(e)) {
    jmethodID harnessConstructor = (*e)->GetMethodID(e, harnessClass, "<init>", "(J)V");
    if (! (*e)->ExceptionCheck(e)) {
        jobject harnessObject = (*e)->NewObject(e, harnessClass, harnessConstructor, (jlong)self);
        if (! (*e)->ExceptionCheck(e)) {
            self.harness = harnessObject;
            (*e)->NewGlobalRef(e, harnessObject);
            self.pauseMethod = (*e)->GetMethodID(e, harnessClass, "appPaused", "()V");
            (*e)->ExceptionCheck(e);
            self.reactivateMethod = (*e)->GetMethodID(e, harnessClass, "appReactivated", "()V");
            (*e)->ExceptionCheck(e);
           
            self.closeMethod = (*e)->GetMethodID(e, harnessClass, "appClosed", "()V");
            (*e)->ExceptionCheck(e);
            self.updateMethod = (*e)->GetMethodID(e, harnessClass, "appUpdate", "()V");
            (*e)->ExceptionCheck(e);
            self.drawMethod = (*e)->GetMethodID(e, harnessClass, "appDraw", "()V");
            (*e)->ExceptionCheck(e);
            self.reshapeMethod = (*e)->GetMethodID(e, harnessClass, "reshapeGame", "(IIII)V");
            (*e)->ExceptionCheck(e);
            self.injectTouchBegin = (*e)->GetMethodID(e, harnessClass, "injectTouchBegin", "(IJFF)V");
            (*e)->ExceptionCheck(e);
            self.injectTouchMove = (*e)->GetMethodID(e, harnessClass, "injectTouchMove", "(IJFF)V");
            (*e)->ExceptionCheck(e);
            self.injectTouchEnd = (*e)->GetMethodID(e, harnessClass, "injectTouchEnd", "(IJFF)V");
            (*e)->ExceptionCheck(e);
            self.IOSInitialiseTouch = (*e)->GetMethodID(e, harnessClass, "IOSInitialiseTouch", "()V");
            (*e)->ExceptionCheck(e);                
        }else{
            NSLog(@"Could not create new iOS Harness object");
            (*e)->ExceptionDescribe(e);
            (*e)->ExceptionClear(e);
            return NO;
        }
    }else{
        NSLog(@"Could not find iOS Harness constructor");
        (*e)->ExceptionDescribe(e);
        (*e)->ExceptionClear(e);
        return NO;
    }
}else{
    (*e)->ExceptionDescribe(e);
    (*e)->ExceptionClear(e);
    NSLog(@"Could not find iOS Harness class");
    return NO;
}
jclass harnessClass = (*e)->FindClass(e, "JmeAppHarness");
if (! (*e)->ExceptionCheck(e)) {
    jmethodID harnessConstructor = (*e)->GetMethodID(e, harnessClass, "<init>", "(J)V");
    if (! (*e)->ExceptionCheck(e)) {
        jobject harnessObject = (*e)->NewObject(e, harnessClass, harnessConstructor, (jlong)self);
        if (! (*e)->ExceptionCheck(e)) {
            self.harness = harnessObject;
            (*e)->NewGlobalRef(e, harnessObject);
            self.pauseMethod = (*e)->GetMethodID(e, harnessClass, "appPaused", "()V");
            (*e)->ExceptionCheck(e);
            self.reactivateMethod = (*e)->GetMethodID(e, harnessClass, "appReactivated", "()V");
            (*e)->ExceptionCheck(e);
           
            self.closeMethod = (*e)->GetMethodID(e, harnessClass, "appClosed", "()V");
            (*e)->ExceptionCheck(e);
            self.updateMethod = (*e)->GetMethodID(e, harnessClass, "appUpdate", "()V");
            (*e)->ExceptionCheck(e);
            self.drawMethod = (*e)->GetMethodID(e, harnessClass, "appDraw", "()V");
            (*e)->ExceptionCheck(e);
            self.reshapeMethod = (*e)->GetMethodID(e, harnessClass, "reshapeGame", "(IIII)V");
            (*e)->ExceptionCheck(e);
            self.injectTouchBegin = (*e)->GetMethodID(e, harnessClass, "injectTouchBegin", "(IJFF)V");
            (*e)->ExceptionCheck(e);
            self.injectTouchMove = (*e)->GetMethodID(e, harnessClass, "injectTouchMove", "(IJFF)V");
            (*e)->ExceptionCheck(e);
            self.injectTouchEnd = (*e)->GetMethodID(e, harnessClass, "injectTouchEnd", "(IJFF)V");
            (*e)->ExceptionCheck(e);
            self.IOSInitialiseTouch = (*e)->GetMethodID(e, harnessClass, "IOSInitialiseTouch", "()V");
            (*e)->ExceptionCheck(e);                
        }else{
            NSLog(@"Could not create new iOS Harness object");
            (*e)->ExceptionDescribe(e);
            (*e)->ExceptionClear(e);
            return NO;
        }
    }else{
        NSLog(@"Could not find iOS Harness constructor");
        (*e)->ExceptionDescribe(e);
        (*e)->ExceptionClear(e);
        return NO;
    }
}else{
    (*e)->ExceptionDescribe(e);
    (*e)->ExceptionClear(e);
    NSLog(@"Could not find iOS Harness class");
    return NO;
}
1 Like

Thanks for the code and the detailed explanation @GTWhite!!! I’ll work on this tomorrow.

Honestly, I thought most of these fixes were already included in jme as they were talked about in the forum… Are you planning on fixing it in the engine? If not, do you mind if I add them in a PR I’m preparing with other ios stuff?

1 Like

not at all, go for it.

If you get stuck let me know because it was bit rushed. I’ll try to get time to tidy it a bit.

1 Like