SystemUI swap & Failed to create frame buffer

Started by Darai, February 10, 2016, 02:15:40 PM

Previous topic - Next topic

Darai

Hello again,

here is a new issue I would like to ask about. I am testing the code with the blitting to texture you gave me (thanks again EgonOlsen so much) http://www.jpct.net/forum2/index.php/topic,4609.0.html. I implemented several performance tests and now I'm trying following situation:

  • Blit to the texture every frame
  • Have more than one object on the screen (in my example 100)
  • Try to call DecorView.setSystemUiVisibility() to show/hide the system icons of the phone. For simple, I have it here initiated by touching the screen

What is the expected problem: The program calls the main activity thread to swap the UI visibility, which initiates new onSurfaceChange run of the renderer and recreates the buffer. And in the same time use intensively the buffer by the blit function on multiple objects.

Here is the entire test activity:
package com.threed.jpct.example;

import java.lang.reflect.Field;
import java.util.Random;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import com.threed.jpct.Camera;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Light;
import com.threed.jpct.Logger;
import com.threed.jpct.Object3D;
import com.threed.jpct.Primitives;
import com.threed.jpct.RGBColor;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.Texture;
import com.threed.jpct.TextureManager;
import com.threed.jpct.World;
import com.threed.jpct.util.BitmapHelper;
import com.threed.jpct.util.MemoryHelper;

/**
* @author Darai
*
*/
public class HelloWorld extends Activity {

private static HelloWorld master = null;

private GLSurfaceView mGLView;
private MyRenderer renderer = null;
private FrameBuffer fb = null;
private World world = null;

private int fps = 0;

private Light sun = null;

private RGBColor back = RGBColor.BLACK;

private static final int SWAP_UI = 1;
private boolean swapUI = false;

private Random rand = new Random();

    // Handle the message for turning on and off the immersive GUI
public static Handler handler = new Handler(Looper.getMainLooper()){
public void handleMessage(Message message) {
    // Handle the message on the thread associated to the given looper.
    if (message.what == HelloWorld.SWAP_UI) { swapSystemUIdo();}
  }
};

public static int countSquares = 0;
public Square[] squares = new Square[100];
public Texture texture;
public static Object frameBufferSync = new Object();

    // We are having 100 squares with blitting to their textures on every tick
public class Square{
private final int curID = countSquares++;
private final String texName = "squateTex/"+curID;
public static final float f = 5f;
public Object3D obj;
public Texture tex;

    // Create texture and object
public Square(){
obj = Primitives.getPlane(1, (float)rand.nextFloat()+0.5f);
tex = new Texture(64,64);
TextureManager.getInstance().addTexture(texName, tex);
world.addObject(obj);
obj.translate(2*rand.nextFloat()*f - f, 2*rand.nextFloat()*f - f, 0);
obj.setTexture(texName);
obj.build();
}

    // redo the texture on every tick (clear and blit)
public void onTick(){
fb.sync();
fb.setRenderTarget(tex);
int col = rand.nextInt(50)+200;
fb.clear(new RGBColor(col,col,col));
int x = convertX(fb, 5);
int y = convertY(fb, tex.getHeight() - 5);
int width = convertX(fb, tex.getWidth()-10);
int height = -convertY(fb, tex.getHeight()-10);
fb.blit(texture, 0, 0, x, y, 64, 64, width, height, -1, false, null);
fb.display();
fb.removeRenderTarget();
}

public int convertX(FrameBuffer buffer, int x) {
return (int) ((float) x * ((float) buffer.getWidth() / (float) tex.getWidth()));
}

public int convertY(FrameBuffer buffer, int y) {
return (int) ((float) y * ((float) buffer.getHeight() / (float) tex.getHeight()));
} }

protected void onCreate(Bundle savedInstanceState) {

Logger.log("onCreate");

if (master != null) {
copy(master);
}

super.onCreate(savedInstanceState);

mGLView = new GLSurfaceView(getApplication());
mGLView.setEGLContextClientVersion(2);

renderer = new MyRenderer();
mGLView.setRenderer(renderer);
setContentView(mGLView);
}

@Override
protected void onPause() {
super.onPause();
mGLView.onPause();
}

    // Swap System UI Send message to the main Activity thread.
public static void swapSystemUI(){
Message message = new Message();
message.what = HelloWorld.SWAP_UI;
HelloWorld.handler.sendMessage(message);
}

    // Swap System UI - do the work (this is the right thread which can do this)
@SuppressLint("InlinedApi")
protected static void swapSystemUIdo() {
Log.i("MainActivity", "swapUI");
    // Set the IMMERSIVE flag.
    // Set the content to appear under the system bars so that the content
    // doesn't resize when the system bars hide and show.
        int uiSetting = HelloWorld.getApp().getWindow().getDecorView().getSystemUiVisibility();
        int ver = Build.VERSION.SDK_INT;
        if(ver>=14){uiSetting ^= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;}
        if(ver>=16){uiSetting ^= View.SYSTEM_UI_FLAG_FULLSCREEN;}
        if(ver>=19){uiSetting ^= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;}       
        HelloWorld.getApp().getWindow().getDecorView().setSystemUiVisibility(uiSetting);
}

@Override
protected void onResume() {
super.onResume();
mGLView.onResume();
}

@Override
protected void onStop() {
super.onStop();
System.exit(0);
}

private void copy(Object src) {
try {
Logger.log("Copying data from master Activity!");
Field[] fs = src.getClass().getDeclaredFields();
for (Field f : fs) {
f.setAccessible(true);
f.set(this, f.get(src));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public boolean onTouchEvent(MotionEvent me) {

if (me.getAction() == MotionEvent.ACTION_DOWN) {
return true;
}

if (me.getAction() == MotionEvent.ACTION_UP) {
swapUI = true;
return true;
}

if (me.getAction() == MotionEvent.ACTION_MOVE) {
return true;
}

try {
Thread.sleep(15);
} catch (Exception e) {
// No need for this...
}

return super.onTouchEvent(me);
}

protected boolean isFullscreenOpaque() {
return true;
}

class MyRenderer implements GLSurfaceView.Renderer {

private long time = System.currentTimeMillis();

public MyRenderer() {
}

public void onSurfaceChanged(GL10 gl, int w, int h) {

// Renew the frame buffer
synchronized(frameBufferSync){
if (fb != null) {
fb.dispose();
}
fb = new FrameBuffer(w, h);
fb.setVirtualDimensions(fb.getWidth(), fb.getHeight());
}

// Create the world if not yet created
if (master == null) {
world = new World();
world.setAmbientLight(20, 20, 20);

sun = new Light(world);
sun.setIntensity(250, 250, 250);

// Create the texture we will use in the blitting
texture = new Texture(BitmapHelper.rescale(BitmapHelper.convert(getResources().getDrawable(R.drawable.icon)), 64, 64));
TextureManager.getInstance().addTexture("texture", texture);

// Create the 100 objects
for(int i = 0;i<squares.length;i++){squares[i]=new Square();}

Camera cam = world.getCamera();
cam.moveCamera(Camera.CAMERA_MOVEOUT, 15);
cam.lookAt(SimpleVector.ORIGIN);

SimpleVector sv = new SimpleVector();
sv.set(SimpleVector.ORIGIN);
sv.y -= 100;
sv.z -= 100;
sun.setPosition(sv);
MemoryHelper.compact();

if (master == null) {
Logger.log("Saving master Activity!");
master = HelloWorld.this;
}
}
}

public void onSurfaceCreated(GL10 gl, EGLConfig config) {
}

public void onDrawFrame(GL10 gl) {

// If the switch is on, call the UI swap in the correct thread
if (swapUI) {
HelloWorld.swapSystemUI();
swapUI = false;
}

synchronized(frameBufferSync){
// Blit to all the objects
for(int i = 0;i<squares.length;i++){squares[i].onTick();}

// Draw the main screen
fb.clear(back);
world.renderScene(fb);
world.draw(fb);
world.getCamera().moveCamera(Camera.CAMERA_MOVEOUT, 0.1f);
world.getCamera().rotateCameraZ(0.01f);

fb.display();
}

if (System.currentTimeMillis() - time >= 1000) {
Logger.log(fps + "fps");
fps = 0;
time = System.currentTimeMillis();
}
fps++;
}
}

public static HelloWorld getApp(){return master;}
}



Well, I have got an error message and app run failure. The error is fixed on the blit function, if I don't blit, it works just fine. The synchronization I tried in this example didn't helped.
Quote
02-10 13:55:58.411: E/jPCT-AE(19599): [ 1455108958411 ] - ERROR: Failed to create frame buffer (102): glError 1282
02-10 13:55:58.470: E/AndroidRuntime(19599): FATAL EXCEPTION: GLThread 7271
02-10 13:55:58.470: E/AndroidRuntime(19599): Process: com.threed.jpct.example, PID: 19599
02-10 13:55:58.470: E/AndroidRuntime(19599): java.lang.RuntimeException: [ 1455108958411 ] - ERROR: Failed to create frame buffer (102): glError 1282
02-10 13:55:58.470: E/AndroidRuntime(19599):    at com.threed.jpct.Logger.log(Logger.java:206)
02-10 13:55:58.470: E/AndroidRuntime(19599):    at com.threed.jpct.GL20.checkError(GL20.java:158)
02-10 13:55:58.470: E/AndroidRuntime(19599):    at com.threed.jpct.GL20.setRenderTarget(GL20.java:2007)
02-10 13:55:58.470: E/AndroidRuntime(19599):    at com.threed.jpct.GLRenderer.setRenderTarget(GLRenderer.java:2111)
02-10 13:55:58.470: E/AndroidRuntime(19599):    at com.threed.jpct.FrameBuffer.setRenderTarget(FrameBuffer.java:260)
02-10 13:55:58.470: E/AndroidRuntime(19599):    at com.threed.jpct.FrameBuffer.setRenderTarget(FrameBuffer.java:222)
02-10 13:55:58.470: E/AndroidRuntime(19599):    at com.threed.jpct.example.HelloWorld$Square.onTick(HelloWorld.java:94)
02-10 13:55:58.470: E/AndroidRuntime(19599):    at com.threed.jpct.example.HelloWorld$MyRenderer.onDrawFrame(HelloWorld.java:277)
02-10 13:55:58.470: E/AndroidRuntime(19599):    at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1535)
02-10 13:55:58.470: E/AndroidRuntime(19599):    at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1240)

Do you EgonOlsen or you guys know what to do with this?
Thanks for any help, Darai.

EgonOlsen

Not sure...might be a kind of threading issue. But you are doing too much work here anyway, IMHO. I would try this:


  • add this call to your GL init code: mGLView.setPreserveEGLContextOnPause(true);
  • in onSurfaceChanged(...) compare the given gl instance (albeit you are not using it later) to the one from the last call of that method. If it has changed, create a new FrameBuffer. If it hasn't, keep the former one.
  • ...see if that helps.

EgonOlsen

If that doesn't help, try to set Config.glDebug=1 before creating the FrameBuffer and post the log output.

Darai

Hi,

thanks for the quick reply. Actualy it is not that complex. I just hitted a real life problem and stripped it to its bare basic = cause the renderer to call the onSurfaceChange when it is doing blitting. Since this (UI swap) is the only situation I know, when I have to call the onSurfaceChange in the middle of the run, this was obvious choise. I used multiple objects for blitting to simulate the real worst case scenario.

To condition the buffer renewal (as you suggested) really helped. Now it works smoothly. But now I am little bit affraid, since I don't know, when is the gl10 variable of the renderer changing... In other words, when can I expect that it will be necessary to recreate the buffer? Can I expect the program to blow up on me in some cases?

public void onSurfaceChanged(GL10 gl, int w, int h) {

// Renew the frame buffer
if(lastGl!=gl){
Log.i("HelloWorld","Init buffer");
if (fb != null) {
fb.dispose();
}
fb = new FrameBuffer(w, h);
fb.setVirtualDimensions(fb.getWidth(), fb.getHeight());
lastGl = gl;
}


And also the example works even without the
mGLView.setPreserveEGLContextOnPause(true);
and I admit that it is my fault that I don't understand what that switch does even after reading it's doc and how is it connected to my GL surface frame buffer problem. So if you will clarify it a little bit or point me to some more information, I will be most grateful.

But even with this information I have from you now, I can continue with the work... so thank you again.

EgonOlsen

#4
As long as the context isn't destroyed, you should get the same GL instance. When it's destroyed is up to Android, you  have no real influence on that. But when it's destroyed, it should be save to do your blitting, because nothing should be left from the "old" instance. I'm not sure what happens here anyway, but I've seen a lot of strange things when it comes to pausing/restarting/stopping and switching Activities...
Did this problem occur everytime you create a new FrameBuffer or just occasionally?

Darai

In the example I wrote here (Heavy misuse of the buffer on every tick) it happend every time I called the swapUI. But I haven't yet enough time to do more test runs to be really sure. And the swapUI utility runs in different thread than the rendering so I don't know if it happend inbetween two onDraw() runs or in the middle of one of them or just randomly every time.

Ok, so since I can expect the buffer to survive and work as long as I will run the app and since there is no problem when I do the first renderring when I start / restart the app I believe it will be ok. Thanks for the clarification.

Darai

Hello,

one (hopefully last) question concerning this topic...
When I stopped destroying the buffer, I also caused following problem: When I "Hide" the UI, the whole screen moves down and creates a rectengle of unused screen above of the size (roughly) of the original UI bar... Can I stretch the original screen somehow to cover also this area? or move it somehow? or how can I get rid of this problem? (Without starting destroying the buffer again since that is the only solution to the previous problem)

I am demonstrating this behaviour on attached screenshots (With / Without soft keys UI) and I am also posting the simple code example. As usual, every help will be most appreciated, thanks.
package com.threed.jpct.example;

import java.lang.reflect.Field;
import java.util.Random;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import com.threed.jpct.Camera;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Light;
import com.threed.jpct.Logger;
import com.threed.jpct.Object3D;
import com.threed.jpct.Primitives;
import com.threed.jpct.RGBColor;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.Texture;
import com.threed.jpct.TextureManager;
import com.threed.jpct.World;
import com.threed.jpct.util.BitmapHelper;
import com.threed.jpct.util.MemoryHelper;

/**
* @author Darai
*
*/
public class HelloWorld extends Activity {

private static HelloWorld master = null;

private GLSurfaceView mGLView;
private MyRenderer renderer = null;
private FrameBuffer fb = null;
private World world = null;

private int fps = 0;

private Light sun = null;

private GL10 lastGl = null;

private RGBColor back = new RGBColor(50,50,10);

private static final int SWAP_UI = 1;
private boolean swapUI = false;

private Random rand = new Random();

    // Handle the message for turning on and off the immersive GUI
public static Handler handler = new Handler(Looper.getMainLooper()){
public void handleMessage(Message message) {
    // Handle the message on the thread associated to the given looper.
    if (message.what == HelloWorld.SWAP_UI) { swapSystemUIdo();}
  }
};

public static int countSquares = 0;
public Object3D obj;
public Texture texture;


protected void onCreate(Bundle savedInstanceState) {

Logger.log("onCreate");

if (master != null) {
copy(master);
}

super.onCreate(savedInstanceState);

mGLView = new GLSurfaceView(getApplication());
mGLView.setEGLContextClientVersion(2);
mGLView.setPreserveEGLContextOnPause(true);

renderer = new MyRenderer();
mGLView.setRenderer(renderer);
setContentView(mGLView);
}

@Override
protected void onPause() {
super.onPause();
mGLView.onPause();
}

    // Swap System UI Send message to the main Activity thread.
public static void swapSystemUI(){
Message message = new Message();
message.what = HelloWorld.SWAP_UI;
HelloWorld.handler.sendMessage(message);
}

    // Swap System UI - do the work (this is the right thread which can do this)
@SuppressLint("InlinedApi")
protected static void swapSystemUIdo() {
Log.i("MainActivity", "swapUI");
    // Set the IMMERSIVE flag.
    // Set the content to appear under the system bars so that the content
    // doesn't resize when the system bars hide and show.
        int uiSetting = HelloWorld.getApp().getWindow().getDecorView().getSystemUiVisibility();
        int ver = Build.VERSION.SDK_INT;
        if(ver>=14){uiSetting ^= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;}
        if(ver>=16){uiSetting ^= View.SYSTEM_UI_FLAG_FULLSCREEN;}
        if(ver>=19){uiSetting ^= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;}       
        HelloWorld.getApp().getWindow().getDecorView().setSystemUiVisibility(uiSetting);
}

@Override
protected void onResume() {
super.onResume();
mGLView.onResume();
}

@Override
protected void onStop() {
super.onStop();
System.exit(0);
}

private void copy(Object src) {
try {
Logger.log("Copying data from master Activity!");
Field[] fs = src.getClass().getDeclaredFields();
for (Field f : fs) {
f.setAccessible(true);
f.set(this, f.get(src));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public boolean onTouchEvent(MotionEvent me) {

if (me.getAction() == MotionEvent.ACTION_DOWN) {
return true;
}

if (me.getAction() == MotionEvent.ACTION_UP) {
swapUI = true;
return true;
}

if (me.getAction() == MotionEvent.ACTION_MOVE) {
return true;
}

try {
Thread.sleep(15);
} catch (Exception e) {
// No need for this...
}

return super.onTouchEvent(me);
}

protected boolean isFullscreenOpaque() {
return true;
}

class MyRenderer implements GLSurfaceView.Renderer {

private long time = System.currentTimeMillis();

public MyRenderer() {
}

public void onSurfaceChanged(GL10 gl, int w, int h) {

// Renew the frame buffer
if(lastGl!=gl){
Log.i("HelloWorld","Init buffer");
if (fb != null) {
fb.dispose();
}
fb = new FrameBuffer(w, h);
fb.setVirtualDimensions(fb.getWidth(), fb.getHeight());
lastGl = gl;
}

// Create the world if not yet created
if (master == null) {
world = new World();
world.setAmbientLight(20, 20, 20);

sun = new Light(world);
sun.setIntensity(250, 250, 250);

// Create the texture we will use in the blitting
texture = new Texture(BitmapHelper.rescale(BitmapHelper.convert(getResources().getDrawable(R.drawable.icon)), 256, 256));
TextureManager.getInstance().addTexture("texture", texture);

// Create the object
obj = Primitives.getPlane(1, 20f);
world.addObject(obj);
obj.translate(0, 0, 0);
obj.setTexture("texture");
obj.build();

Camera cam = world.getCamera();
cam.moveCamera(Camera.CAMERA_MOVEOUT, 15);
cam.lookAt(SimpleVector.ORIGIN);

SimpleVector sv = new SimpleVector();
sv.set(SimpleVector.ORIGIN);
sv.y -= 100;
sv.z -= 100;
sun.setPosition(sv);
MemoryHelper.compact();

if (master == null) {
Logger.log("Saving master Activity!");
master = HelloWorld.this;
}
}
}

public void onSurfaceCreated(GL10 gl, EGLConfig config) {
}

public void onDrawFrame(GL10 gl) {

// If the switch is on, call the UI swap in the correct thread
if (swapUI) {
HelloWorld.swapSystemUI();
swapUI = false;
}

// Draw the main screen
fb.clear(back);
world.renderScene(fb);
world.draw(fb);
fb.display();

if (System.currentTimeMillis() - time >= 1000) {
Logger.log(fps + "fps");
fps = 0;
time = System.currentTimeMillis();
}
fps++;
}
}

public static HelloWorld getApp(){return master;}
}



EgonOlsen

#7
Yes. Just add this as an else-branch to your lastGl!=gl condition:


else {
fb.resize(w, h);
fb.setVirtualDimensions(w, h);
}