android viewpger and renderer on each page cause artefacts and finnaly crash

Started by arekz, July 11, 2016, 01:27:48 PM

Previous topic - Next topic

arekz

Hi,
I'm new in jPCT and I think I miss some thing and I have very strange behavior.
I made simple activity with PageViewer  https://developer.android.com/reference/android/support/v4/view/ViewPager.html
on each page (android fragment) I put MyRenderer from HelloWord example (I only modified to load obj model).

The important feature of PageViewer is that it load 3 fragments (one main and one on left and one on right) as visible and shows only main. Reason for that is to prepare next page to show to user.
So MyRenderer is run 2 or 3 times. And I suspect that this is the problem. But how to fix/solve/workaround this?

When start of app after while (5-9 sek.) I get crash:

E/AndroidRuntime: FATAL EXCEPTION: GLThread 32054
                                                                                   Process: pl.example.test, PID: 15106
                                                                                   java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0
                                                                                       at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
                                                                                       at java.util.ArrayList.get(ArrayList.java:308)
                                                                                       at com.threed.jpct.Object3D.render(Object3D.java:6271)
                                                                                       at com.threed.jpct.World.renderScene(World.java:1075)
                                                                                       at pl.example.test.MyRenderer.onDrawFrame(MyRenderer.java:155)
                                                                                       at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1535)
                                                                                       at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1240)

Before crash I see the loaded model but disappearing for 200ms or so.


public class MyPageFragment extends Fragment {

    int position = -1;

    private GLSurfaceView mGLView;

    @BindView(R.id.model_3d_frame)
    FrameLayout model3dFrame;

    BoatRenderer renderer;

    public static MyPageFragment newInstance(int position) {

        Bundle args = new Bundle();
        args.putInt(ARG_POSITION, position);

        MyPageFragment fragment = new MyPageFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        position = getArguments().getInt(ARG_POSITION);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.one_model3d_view_fragment, container, false);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        ButterKnife.bind(this, view);
        setupGLView(getActivity(), model3dFrame);
    }

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

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

    private void setupGLView(Context context, ViewGroup container) {
        mGLView = new GLSurfaceView(context.getApplicationContext());
        mGLView.setEGLContextClientVersion(2);

        try {

            renderer = new BoatRenderer(context);

            mGLView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
            mGLView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
            mGLView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

            mGLView.setZOrderOnTop(true);
            mGLView.setRenderer(renderer);

            container.addView(mGLView);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}


class MyRenderer implements GLSurfaceView.Renderer {

    private static final String TAG = MyRenderer.class.getSimpleName();

    private int fps = 0;
    private Light sun = null;
    private FrameBuffer fb = null;
    private World world = null;
    private long time = System.currentTimeMillis();
    final Object3D m32;
    boolean isSetMaster = false;

    public MyRenderer(Context context) throws IOException {
        m32 = loadM32(context, 20.5f);
    }

    Object3D loadM32(final Context context, final float scale) throws IOException {
        Log.d(TAG,"loadM32 model");

        final Object3D[] objs;
        objs = Loader.loadOBJ(context.getAssets().open("models/m32/m32_02.obj"), context.getAssets().open("models/m32/m32_02.mtl"), scale);

        if (!TextureManager.getInstance().containsTexture("K"))
            TextureManager.getInstance().addTexture("K", new Texture(getBitmapFromAsset(context, "models/m32/k_tex.png")));

        if (!TextureManager.getInstance().containsTexture("F"))
            TextureManager.getInstance().addTexture("F", new Texture(getBitmapFromAsset(context, "models/m32/f_tex.jpg")));

        if (!TextureManager.getInstance().containsTexture("G"))
            TextureManager.getInstance().addTexture("G", new Texture(getBitmapFromAsset(context, "models/m32/g_tex.jpg")));

        objs[0].setTexture("F");
        objs[1].setTexture("G");
        objs[2].setTexture("K");

        return Object3D.mergeAll(objs);
    }

    public void onSurfaceChanged(GL10 gl, int w, int h) {
        Log.d(TAG, "onSurfaceChanged( "+w +" x " + h +")");
        if (fb != null) {
            fb.dispose();
        }
        fb = new FrameBuffer(w, h); // OpenGL ES 2.0 constructor

        if (!isSetMaster) {

            world = new World();
            world.setAmbientLight(20, 20, 20);

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

            world.addObjects(m32);

            Camera cam = world.getCamera();
            cam.moveCamera(Camera.CAMERA_MOVEOUT, 50);
            cam.lookAt(m32.getTransformedCenter());

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

            if (!isSetMaster) {
                Logger.log("Saving master Activity!");
                isSetMaster = true;
            }
        }
    }

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

    public void onDrawFrame(GL10 gl) {

        fb.clear();
        world.renderScene(fb);
        world.draw(fb);
        fb.display();

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

    public static Bitmap getBitmapFromAsset(Context context, String filePath) {
        AssetManager assetManager = context.getAssets();

        InputStream istr;
        Bitmap bitmap = null;
        try {
            istr = assetManager.open(filePath);
            bitmap = BitmapFactory.decodeStream(istr);
        } catch (IOException e) {
            // handle exception
        }

        return bitmap;
    }
}



EgonOlsen

I'm not quite sure what exactly you are doing...you have multiple instances of MyRenderer running in parallel on the same world instance?

arekz

Quote from: EgonOlsen on July 11, 2016, 02:45:44 PM
I'm not quite sure what exactly you are doing...you have multiple instances of MyRenderer running in parallel on the same world instance?
I have multiple instances of MyRenderer running in parallel on multiple world instances. Each instance of MyRenderer has one instance of world class. (MyRenderer has private field for world instance).
But because of ViewPager I have 2 (or 3) MyRenderer instances.

My app shows 3d models of furniture with descriptions and etc. So on each page I want to show 3d model of piece of furniture. So I get the HelloWord app and move rendering to the android fragment.

When I limit ViewPager to show only one page it's work correctly. Problem appears only if I have to show more that one page.

arekz

the crash report point to line:

        world.renderScene(fb);

in MyRenderer

EgonOlsen

Can you please test, if all these render calls are running into the same thread? Or does multiple views mean multiple threads?

arekz

As I know Android API the pages in ViewPager run in same thread (the UI thread). But on each page I have  GLSurfaceView instance  so  if GLSurfaceView runs separated thread for rendering then yes, I have multiple threads.
I workaround the problem, but I'm not  glad with this solution.
In short: i detect if fragment is visible to the user and then call glSurfaceView.onResume() if fragment goes out of screen then I call glSurfaceView.onPasue() this cause that only one GLSurfaceView is in state Resumed.
But this solution has some side effect: the models aren't preloaded and loaded when page are start showing to a user.

I hope that this workaround give you a clue what is root reason of issue and maybe you find a good solution :D

EgonOlsen

jPCT-AE isn't thread safe, so it's not supposed to be used by several threads in parallel. This also applies to multiple instances. However, I'm not sure if that's the actual problem here, because it doesn't really explain the exception that you are getting, nor do I understand why views that aren't visible are getting rendered!? Isn't that wrong anyway?
Can you please verify, if these GLSurfaceView instances are really running in multiple threads (just print out the Thread instance in each)?