[SOLVED] Unable to start app with offscreen rendering on Android

Hello.

I’m trying to implement offscreen rendering on Android. On desktop I simply call start(JmeContext.Type.OffscreenSurface) method from LegacyApplication and engine starts to initialize itself. On android it seems that nothing happens after I call start(JmeContext.Type.OffscreenSurface). simpleInitApp() does not get called and I do not see any activity in the logs.

Is it possible to use OffscreenSurface mode on Android at all?

You can render jme on a JmeSurfaceView and display it later on the screen, but what are you trying to do ?

EDIT :
Btw, Android uses GlSurfaceView and GlSurfaceView.Renderer to render jMonkeyEngine so no lwjgl.

I want to generate an image and save it locally without displaying anything on screen. I just press a button, the app renders an image in a memory and save it on a device.

Well, i haven’t tried that before, so you have multiple solutions :

  1. Use gl.glReadPixels() in GLSurfaceView#onDrawFrame(), but you will have to access the internals of jme android OGLESContext#onDrawFrame() to inject an action into the onDrawFrame().

  2. I think there is already a ScreenShotAppState for android check it out :
    jmonkeyengine/AndroidScreenshots.java at d6950889f6db58b63cbdaae5e29cd2fb0a0d4452 · jMonkeyEngine/jmonkeyengine · GitHub

Thanks, I’ll will take a look.

But still there is a question how to initialize an engine?
LegacyApplication.start(JmeContext.Type.OffscreenSurface) seems does not initialize a rendering pipeline at all. Maybe I’m doing something wrong.
Does anybody know is offscreen rendering is implemented on Android?
Or I should create a View and set it as render target for jme, but will it render on it if it will not be added to the interface?

A JmeSurfaceView is an android RelativeLayout that holds a GlSurfaceView upon which a GlSurfaceView.Renderer can initialize and update a jme application, this is an example code :

public class MainActivity extends AppCompatActivity implements OnRendererCompleted, OnExceptionThrown {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        /*define the android view with it's id from xml*/
        JmeSurfaceView jmeSurfaceView =  new JmeSurfaceView(this);
        /*set the jme game*/
        jmeSurfaceView.setLegacyApplication(new MyGame());
        jmeSurfaceView.setOnExceptionThrown(this);
        jmeSurfaceView.setOnRendererCompleted(this);
        /*start the game*/
        jmeSurfaceView.startRenderer(JmeSurfaceView.NO_DELAY);
        /*set your view as activity content */ 
       setContentView(jmeSurfaceView);
    }
}

More at :

EDIT :
you can skip the setContentView(jmeSurfaceView); part which displays the game on your screen :slight_smile: , from jmeSurafecView you can getLegacyApplication() and then convert the frame buffer into byte buffer and output the result to a Bitmap image using Screenshots appstate.

LegacyApplication#start() on android starts a blank jMonkeyEngine context, so you will need to bind jme application lifecycle to glSurfaceView renderer, JmeSurfaceView and OGLESContext already does this, so you will only need to use JmeSurfaceView,

Just tried this approach. If I skip setContentView(jmeSurfaceView); then simpleInitApp() is not get called either. It seems that rendering initializes only when view is attached to a layout…

1 Like

Hmm, a hack is to view.setVisibility(View.Gone) after setcontentview…could you please try ?

Tried, no success. (setContentView() replaces current layout so I use addView() but I think it makes no difference)
When I use view.setVisibility(View.Gone) nothing happens. Without it after view is attached to a layout - jme starts to initialize rendering

1 Like

setContentView() sets the layout view for the whole activity, alright anyway let me try something and send you if there is a success :wink:

@Kroart Alright, you have 2 options to try :

  1. Use view.setVisibility(View.INVISIBLE) and it differs from GONE that it applies something like an alpha map that helps to keep the view out of color but the view isn’t gone.

  2. Use OnRendererCompleted listener to listen for renderer status and set the view as gone when rendering is completed :

  /**
     * Fired when the user delay in ms is up #{@link JmeSurfaceView#startRenderer(int)}.
     * @param application the current jme game instance
     * @param appSettings the current game settings
     */
    @Override
    public void onRenderCompletion(LegacyApplication application, AppSettings appSettings) {
        findViewById(R.id.jmeSurfaceView).setVisibility(View.GONE);
    }

i think better make it invisible so to be still visible to the renderer lifecycle…

1 Like

So I answered for my first question. Offscreen rendering is not implemented on Android. When calling LegacyApplication.start(JmeContext.Type.OffscreenSurface) it calls JmeAndroidSystem.newContext(AppSettings settings, Type contextType) and contextType is ignored there. It always creates context with Display rendering type.

So now the question is there is possible to create GLSurfaceView (or JmeSurfaceView) and set rendering to it without adding it to the layoyt.

1 Like

Yeah, but that’s not true, either try the INVISIBLE or set it as a background with an overlay relative layout that holds your controls.

Thanks, will try these options

1 Like
    /**
     * Fired when the user delay in ms is up #{@link JmeSurfaceView#startRenderer(int)}.
     * @param application the current jme game instance
     * @param appSettings the current game settings
     */
    @Override
    public void onRenderCompletion(LegacyApplication application, AppSettings appSettings) {
   
        jmeSurfaceView.setLayoutParams(new FrameLayout.LayoutParams(0, 0));
    }

This also works btw.

In my understanding it will create surface with zero size so I will not be able to render anything on it. But I need to render scene on the surface and then get the image data from it and store it as image file.

1 Like

Then yeah, the best way to go is the INVISIBLE or is to lay jmeSurfaceView behind your activity layout so, the user cannot see it :slightly_smiling_face: or use both.

Sorry, misunderstood your example)

Just tried to use INVISIBLE and it does not work either. Investigating further…

Hmm, but i tried the INVISIBLE and it did work, can you log the simpleInit() and jme update so you are sure isnot working ?

Hmm, when I’m set INVISIBLE for JmeSurfaceView it still became visible for me. And when I’m trying to make INVISIBLE for GLSurfaceView then rendering does not started. Need to look further…

1 Like