Settings.setResolution does not work on android

In my SimpleApplication I override the setSettings method to change the resolution:

[java] @Override
public void setSettings(AppSettings settings) {
settings.setResolution(132,80);
Log.i(LOG_TAG, "Changing resolution to: " + settings.getWidth() + “x”
+ settings.getHeight());
super.setSettings(settings);
}[/java]

in the simpleInitApp() method I logged the resolition: “INFO Log 11:24:32 using resolution : 132x80”

so it worked to set in in the app settings, but when I check the game on the phone it has still the same resoltion. Is that because the opengl surface view uses the complete devices screen and theirfor just ignores the defined resultion of 132x80 and just uses the size of the opengl surface view which is then again the full resolution of the screen? I wanted to reduce the resolution to improve the performance (of course not to 132x80 but to something lower then the max resolution of the phone screen)

Have you tried calling super.setSettings(settings) after line 3?
Your code looks to me like it’s short-circuitting the AppSettings logic in Application, so settings don’t get applied at all. If the resolution doesn’t get applied on the desktop, then that’s a problem you’re having on top of any Android specifics you might be having.

@toolforger said: Have you tried calling `super.setSettings(settings)` after line 3? Your code looks to me like it's short-circuitting the AppSettings logic in Application, so settings don't get applied at all. If the resolution doesn't get applied on the desktop, then that's a problem you're having on top of any Android specifics you might be having.

sorry I did not paste the full code and forgot the super call in my text, i changed it now

Ok i found a solution, but it completelly ignores the values in the settings object now. I now directly talk to the AndroidGLSurfaceView like this:

[java]int width = 132 ;
int heigth = 80 ;
glSurfaceView.getHolder().setFixedSize(width, heigth);[/java]

now the internal resolution is used like i want to and i still can strech it to the full window size. The performance improvements on low end devices are realy nice :slight_smile: i think that should be added as a optional feature in the AndroidHarness, so allow the developer to tell the AndroidHarness which resolution to use.

I suspect something else is reconfiguring the resolution after you have modified the Settings object.
You might want to find that out, to prevent funny effects as the circumstances vary (e.g. different versions of Android might or might not like to have the resolution changed very often, future versions of JME might or might not work with that workaround, etc.).

I wonder if this could be done at run time, similar to how watching live online streams and youtube does it. If theres a network spike, the resolution is set lower, so that you can still watch, but the quality decreases. Perhaps something like this could be added, where if the frame rate is low ingame (due to many objects etc… or is just a poor device), a lower resolution is used to increase the FPS, hmm

1 Like
@toolforger said: I suspect something else is reconfiguring the resolution after you have modified the Settings object. You might want to find that out, to prevent funny effects as the circumstances vary (e.g. different versions of Android might or might not like to have the resolution changed very often, future versions of JME might or might not work with that workaround, etc.).

settings.setResolution() is only called once in the AndroidHarness and after that call the settings object is not modified anymore. It tries to use the screen size of the device as the resolution but the glSurfaceView doesn’t care and uses the screensize anyways no matter what is passed in the settings object.

the glSurfaceView.getHolder().setFixedSize(…) method is never called, also not in the OGLESContext ( Google Code Archive - Long-term storage for Google Code Project Hosting. ) class. I dont think that a manually defined resolution works at the moment on Android phones, from what i have read now it only works when using the glSurfaceView.getHolder().setFixedSize() method.

@wezrule said: I wonder if this could be done at run time, similar to how watching live online streams and youtube does it. If theres a network spike, the resolution is set lower, so that you can still watch, but the quality decreases. Perhaps something like this could be added, where if the frame rate is low ingame (due to many objects etc.. or is just a poor device), a lower resolution is used to increase the FPS, hmm

uh that would be a great feature, I will try out if it works

1 Like
@wezrule said: I wonder if this could be done at run time, similar to how watching live online streams and youtube does it. If theres a network spike, the resolution is set lower, so that you can still watch, but the quality decreases. Perhaps something like this could be added, where if the frame rate is low ingame (due to many objects etc.. or is just a poor device), a lower resolution is used to increase the FPS, hmm

Ok it works great on runtime, I testet it on an Galaxy Nexus and an S3 and on both there is a short flashing of the screen and then the resolution is higher or lower. On the Nexus I had an fps of 12 and switched to 1/3 of the original resolution and then had 50fps. The flashing is a little bit annoying so maybe not the best idea to switch between resolutions too often but it would work to slowly reduce the resolution when the fps are getting to low.

1 Like

ah awesome investigation :), the flickering might be able to prevented by using the back buffer possibly, @nehon, @iwgeric what do you think about this?

1 Like

I started looking at this today. I can see a couple of issues with using a fixed resolution. If the game starts in landscape mode and the device is then rotated, we can’t just automatically use the same fixed resolution. Also, running the app on a phone vs. a tablet will have very different starting resolutions and we wouldn’t want to have to manually manage multiple fixed resolutions based on the actual device capabilities.

It might be a good idea to allow for app developers to define a multiplier instead of an absolute resolution setting. The multiplier could then be applied in either orientation based on the current display resolution. I haven’t looked into this much yet, but there might be some issues that require only certain settings (like forcing only increments of 50% or something to avoid weird width/height combinations).

Do you really want to define the absolute resolution in this case, or do you just want to adjust it based on performance?

I think something like this would be cool:

  • Developer (us), specifies a frame rate, for when our app will automatically lower the resolution, i.e when FPS below 30, a lower resolution is used, above 45 a higher one is used or something
  • Player loads app
  • App finds all suitable resolutions for the phone in question, 1920 x 1080px, 1024 x 768px, 640 x 480px, 320 x 240px etc
  • Load default settings
  • Game Starts
  • Record the FPS over a few frames, and take an average of that. If below or above the ranges, then change resolution.

Useful for:

  • Supporting more crappy devices
  • Maintains fluent gameplay when there are high loads on the graphics processor, due to many objects in view or something

here are my experiences i had so far:

  • I used a multiplier value (steps of 10%) and had no problems on the 4 devices I tested it on
  • the first time a new textured model is displayed opengl often throws an out of memory error, when i reduce the resolution to 1/2 of the max one, it does not happen so often. any ideas why?
  • on devices like the s3 or the htc one the max resolution is way to high per default which makes most tests very slow also on high end devices like this, so a default resolution of 1/2 should be a good idea

Most of this can already be done without any core changes. view (from AndroidHarness) is already exposed to MainActivity, so the resolution can be adjusted on the fly. When the resolution of the view is changed, the app settings are automatically updated and the reshape() method is called. This is in OGLESContext in the onSurfaceChanged() method which is called automatically by the surfaceview when the resolution is changed.

I don’t think this should be in core as an automatic feature. One of the issue I see is determining the appropriate time to change the resolution and how much to adjust the resolution by. I think this should be a decision left to the app developer based on the game performance requirements. If they are going to do that, they might as well also put code in to fire the change when they think it’s appropriate.

Below are some screenshots before and after the resolution change and the example I used to do it without any core changes. As you can see, I did not put in code to adjust the size of the guiNode objects (stats view) based on the new screen width / height which would typically be done for guiNode objects. In a real app, the app developer would need to call a reshape method in their screen manager / gui manager based on app.reshape(width, height) to adjust the size and placement of any guiNode objects.

[EDIT] Ignore the frames per second in the before image, it is 1 due to the screenshot being taken. It was actually 59fps as well.

Before Resolution Change:

After Resolution Change:

[java]
package com.jmonkeyengine.autoresolution;

import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.view.Display;
import android.view.WindowManager;
import com.jme3.app.AndroidHarness;
import com.jme3.system.android.AndroidConfigChooser.ConfigType;
import java.util.logging.Level;
import java.util.logging.LogManager;

public class MainActivity extends AndroidHarness{
int displayWidth = 0;
int displayHeight = 0;
int fixedWidth = 0;
int fixedHeight = 0;
float totalTime = 0f;
boolean needNewResolution = true;

/*
 * Note that you can ignore the errors displayed in this file,
 * the android project will build regardless.
 * Install the 'Android' plugin under Tools->Plugins->Available Plugins
 * to get error checks and code completion for the Android project files.
 */

public MainActivity(){
    // Set the application class to run
    appClass = "mygame.Main";
    // Try ConfigType.FASTEST; or ConfigType.LEGACY if you have problems
    eglConfigType = ConfigType.BEST;
    // Exit Dialog title & message
    exitDialogTitle = "Exit?";
    exitDialogMessage = "Press Yes";
    // Enable verbose logging
    eglConfigVerboseLogging = false;
    // Choose screen orientation
    screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
    // Enable MouseEvents being generated from TouchEvents (default = true)
    mouseEventsEnabled = true;
    // Set the default logging level (default=Level.INFO, Level.ALL=All Debug Info)
    LogManager.getLogManager().getLogger("").setLevel(Level.FINE);
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    /* get the default display resolution from the screen */
    WindowManager wind = this.getWindowManager();
    Display disp = wind.getDefaultDisplay();
    logger.log(Level.INFO, "Resolution from Window, width: {0}, height: {1}",
            new Object[]{disp.getWidth(), disp.getHeight()});
    displayWidth = disp.getWidth();
    displayHeight = disp.getHeight();
}

@Override
public void update() {
    super.update();
    float tpf = app.getTimer().getTimePerFrame();
    totalTime += tpf;

    /* modify this check to be based on tpf < targetTPF if desired */
    if (totalTime > 10f && needNewResolution) {
        adjustResolution(.5f);
        needNewResolution = false;
    }
}

protected void adjustResolution(float adjustmentFactor) {
    if (adjustmentFactor == 0) {
        logger.log(Level.INFO, "Skipping Adjustment, factor == 0");
        return;
    }

    logger.log(Level.INFO, "Current Resolution is width: {0}, height: {1}",
            new Object[]{fixedWidth, fixedHeight});
    if (fixedWidth == 0 || fixedHeight == 0) {
        fixedWidth = (int)(displayWidth * adjustmentFactor);
        fixedHeight = (int)(displayHeight * adjustmentFactor);
    } else {
        fixedWidth = (int)(fixedWidth * adjustmentFactor);
        fixedHeight = (int)(fixedHeight * adjustmentFactor);
    }
    logger.log(Level.INFO, "Setting Fixed Resolution to width: {0}, height: {1}",
            new Object[]{fixedWidth, fixedHeight});


    // if view is already created, force the update to the resolution so that
    //   OGLESContext.onSurfaceChanged will be called by the surface view
    //   which will update the appsettings resolution and call app.reshape
    if (view != null) {
        this.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                logger.log(Level.INFO, "View already created, adjusting to fixed resolution.");
                // settings are adjusted in onSurfaceChanged in OGLESContext
                view.getHolder().setFixedSize(fixedWidth, fixedHeight);
            }
        });
    }
}

}
[/java]

4 Likes

awesome :), did you notice any of this “flashing of the screen” that simon experienced? and have you tried it on a complex scene, and was there much difference in performance to warrant using this?

I didn’t do any complex screen testing. I believe simon already did that and saw an improvement.

I did see a flicker for 1 frame (I’m guessing here). I haven’t look too hard into that, but it may be due to a difference between when the resolution takes effect vs when the reshape is called and the viewports being updated (again, just guessing here).

1 Like

@iwgeric
Sorry for drudging up an old thread, however… I’ve noticed a few things using this.

  1. If you bypass the update loop and just set resolution scaling to 1, frame rate issues that were there prior go away /boggle - I can’t reiterate this enough times. This sorta freaks me out, as I can’t help but wonder why there were frame rate issues to begin with.
  2. I don’t know if this is on all devices, but on my tablet, application scaling seems to break ray casting.

EDIT: 2 is wrong…

[java]
this.runOnUiThread(new Runnable() {
@Override
public void run() {
// settings are adjusted in onSurfaceChanged in OGLESContext
view.getHolder().setFixedSize(fixedWidth, fixedHeight);
}
});
[/java]

breaks ray casting on the gui layer.

EDIT 2: Just to be clear on point 2: I removed all application setup from simpleInitApp and moved it into the update loop with a flag for running it only once to verify that it wasn’t some dependency I may have been missing. Nothing is added to the app until after the above is set and this still have a negative impact on ray casting (at least in the gui node). Haven’t tested anything past this.

Have you experienced either of these 2 things?

@t0neg0d

I have not used this method myself, so I don’t have any personal experience.

I’ll try to do some quick tests to see if the coordinates returned from Android are affected by the resolution change.

@t0neg0d said: 1. If you bypass the update loop and just set resolution scaling to 1, frame rate issues that were there prior go away /boggle - I can't reiterate this enough times. This sorta freaks me out, as I can't help but wonder why there were frame rate issues to begin with.

I assume you’re talking about the fps issue you had on your tablet. Can you explain a little bit what bypassing the update loop means? Do you mean you remove the code in your app from executing during update?

@t0neg0d said: 2. I don't know if this is on all devices, but on my tablet, application scaling seems to break ray casting.

I finally had a chance to look at this. Sorry it took so long.

The issue is that Android is reporting the onTouch coordinates relative to the width/height of the view which is still at the same size. Since the surfaceview is scaled down, the touch coordinates being sent to jME are not correct. They need to be scaled based on the scaling used in view.getHolder().setFixedSize(int, int);

I’m working on what to do to have jME deal with this automatically, but it will be a 3.1 patch, not 3.0.

In the meantime, if you save the resolution you are using in setFixedSize, you can create a scale factor that you can then use to adjust the x and y coordinates in your input listeners.

@t0neg0d said: 2. I don't know if this is on all devices, but on my tablet, application scaling seems to break ray casting.

Just to be complete, here is what I did to make it work:

1.) In simpleInitApp, store the initial width and height from appsettings
2.) when a touch event happens and you want to use the touch x / y for something like ray casting, adjust the x and y location by the ratio of the original width and height.

When setFixedSize is used from within onCreate in MainActivity, it does not notify jME until the 2nd jME render frame. The first frame creates the surface view and runs the app’s simpleInitApp. The second frame is when the surfaceview notifies jME that the surface size has changed. That’s when the onSurfaceChanged is fired and the appsettings are adjusted (along with camera, etc.). This means you can grab the settings from within simpleInitApp which is run before the resolution is changed. Then, you can adjust the touch location when you need to later.

[java]
public class Main extends SimpleApplication {
private int displayWidth;
private int displayHeight;

@Override
public void simpleInitApp() {
    displayWidth = settings.getWidth();
    displayHeight = settings.getHeight();
}


// called from some inputListener (ie. onTouchEvent)
private void pickObject() {
    CollisionResults results;

    Vector2f curClick2d = new Vector2f();
    logger.log(Level.INFO, "display width: {0}, height: {1}", 
            new Object[]{displayWidth, displayHeight});
    logger.log(Level.INFO, "settings width: {0}, height: {1}", 
            new Object[]{settings.getWidth(), settings.getHeight()});
    curClick2d.x = inputManager.getCursorPosition().x * ((float)settings.getWidth() / (float)displayWidth);
    curClick2d.y = inputManager.getCursorPosition().y * ((float)settings.getHeight() / (float)displayHeight);
    logger.log(Level.INFO, "curClick2d: {0}", curClick2d);
    
    // Pick Root Node (3D)
    Vector3f click3d = cam.getWorldCoordinates(
        new Vector2f(curClick2d.x, curClick2d.y), 0f).clone();
    Vector3f dir = cam.getWorldCoordinates(
        new Vector2f(curClick2d.x, curClick2d.y), 1f).subtractLocal(click3d).normalizeLocal();
    Ray ray = new Ray(click3d, dir);

    results = new CollisionResults();
    rootNode.collideWith(ray, results);
    
    logger.log(Level.INFO, "3D result size: {0}", results.size());
    if (results.size() > 0){
        for (int i=0; i<results.size(); i++) {
            CollisionResult result = results.getCollision(i);
            Geometry geo = result.getGeometry();
            logger.log(Level.INFO, "3D object: {0}, at x: {1}, distance: {2}", 
                    new Object[]{geo.getName(), result.getContactPoint(), result.getDistance()});
        }
    } else {
        logger.log(Level.INFO, "no 3D hits found start: {0}, dir: {1}", 
                new Object[]{click3d, dir});
    }
    
    // Pick Gui Node (2D)
    Vector3f start = new Vector3f(curClick2d.x, curClick2d.y, 10f);
    dir = Vector3f.UNIT_Z.negate();
    ray = new Ray(start, dir);

    results = new CollisionResults();
    guiNode.collideWith(ray, results);
    
    logger.log(Level.INFO, "2D result size: {0}", results.size());
    if (results.size() > 0){
        for (int i=0; i<results.size(); i++) {
            CollisionResult result = results.getCollision(i);
            Geometry geo = result.getGeometry();
            logger.log(Level.INFO, "2D object: {0}, at x: {1}, distance: {2}", 
                    new Object[]{geo.getName(), result.getContactPoint(), result.getDistance()});
        }
    } else {
        logger.log(Level.INFO, "no 2D hits found");
    }
    

    
}

[/java]

2 Likes