Viable engine?

Started by MrAdam, May 02, 2012, 05:04:46 PM

Previous topic - Next topic

MrAdam

Hey there. I'm an Android developer working at a social media company in Denmark.
We recently started a project for a big customer, which requires us to show a high-poly model to the users.
So far we've tried using JMonkeyEngine, but it's performance is not nearly good enough.

We have a very detailed 3D model of a car, consisting of over 100k vertices.
Its using shaders for coloring and for the windows.

Our main problem is the memory limitations of Android devices, with most of them running out of memory trying to load the model.
I had a go at your engine, and noticed improved loading times compared to JME, but some old (and new) devices would still run out of memory during the loading.

One of your demo apps in the play store (terrain benchmark) had a vertex count of over 100k, and loaded in no time at all.
I presume its using your DeSerializer, which I've tried using, but saving the serialized object using the desktop library is using a newer version of the format, which makes it impossible to load it with the Android library.

So, my question is. Do you think your engine will be usable in regards to loading/showing a high-poly model with shaders?

Best regards, Adam Honoré

EgonOlsen

There shouldn't be any version mismatch between serialized files...try the jars mentioned in this post: http://www.jpct.net/forum2/index.php/topic,2708.msg20009.html#msg20009

They both work with version 4 of the serialized file format, so it should be fine.

If it's usable or not depends on your requirements. The serialized format is the fastest and most memory saving format you'll get from this engine.

MrAdam

#2
Thanks. I got the loading to work with the beta, file format version matches desktop version.

Although, Ive run into a new problem with memory limits. The original 3DS file is 1.7 mb. and the serialized model is 6.4 mb.
Our old test phones die with out of memory when loading the serialized model, almost right away. (They have 24 mb heap).
But on newer phones with 32 mb heap it loads all the way, right until around the first render, then it spits out this:
05-03 11:10:09.798: E/dalvikvm-heap(21685): 287964-byte external allocation too large for this process.

What confuses me is that in your AN3DBenchXL "terrain" bench even the 24 mb heap phones is able to load the scene, but they won't load my 80k vertices model.

Ive uploaded the 3DS model to dropbox so you can see what we are dealing with:
http://dl.dropbox.com/u/63940770/basic_final80k.3DS

EDIT: It seems to me like some part of the engine might not be buffered

EgonOlsen

I'm not sure what you mean with your EDIT...???

Anyway, i serialized the model using this code:


import java.io.FileNotFoundException;
import java.io.FileOutputStream;

import com.threed.jpct.Config;
import com.threed.jpct.DeSerializer;
import com.threed.jpct.Loader;
import com.threed.jpct.Object3D;

public class Serrer {

public static void main(String[] args) throws FileNotFoundException {
Config.oldStyle3DSLoader=true;
Object3D[] objs = Loader.load3DS("basic_final80k.3DS", 1);
for (Object3D obj : objs) {
obj.build();
}

new DeSerializer().serializeArray(objs, new FileOutputStream("car.ser"), true);
}
}


As you can see, i preserved the single objects (i.e. i didn't merge them). You'll need this for transparency anyway and the resulting file is smaller, because it contains less empty space).

I then loaded it using this hacky code:

package com.threed.jpct.test.car;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.zip.ZipInputStream;

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

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.view.MotionEvent;

import com.threed.jpct.Camera;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Light;
import com.threed.jpct.Loader;
import com.threed.jpct.Logger;
import com.threed.jpct.Object3D;
import com.threed.jpct.RGBColor;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.World;
import com.threed.jpct.util.MemoryHelper;

public class CarTestActivity extends Activity {

private static CarTestActivity master = null;

private GLSurfaceView mGLView;
private MyRenderer renderer = null;
private FrameBuffer fb = null;
private World world = null;
private RGBColor back = new RGBColor(50, 50, 100);

private float touchTurn = 0;
private float touchTurnUp = 0;

private float xpos = -1;
private float ypos = -1;

private Object3D[] parts = null;
private int fps = 0;

private Light sun = null;

protected void onCreate(Bundle savedInstanceState) {

Logger.log("onCreate");

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

super.onCreate(savedInstanceState);
mGLView = new GLSurfaceView(getApplication());

mGLView.setEGLConfigChooser(new GLSurfaceView.EGLConfigChooser() {
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
int[] attributes = new int[] { EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_NONE };
EGLConfig[] configs = new EGLConfig[1];
int[] result = new int[1];
egl.eglChooseConfig(display, attributes, configs, 1, result);
return configs[0];
}
});

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

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

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

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

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) {
xpos = me.getX();
ypos = me.getY();
return true;
}

if (me.getAction() == MotionEvent.ACTION_UP) {
xpos = -1;
ypos = -1;
touchTurn = 0;
touchTurnUp = 0;
return true;
}

if (me.getAction() == MotionEvent.ACTION_MOVE) {
float xd = me.getX() - xpos;
float yd = me.getY() - ypos;

xpos = me.getX();
ypos = me.getY();

touchTurn = xd / -100f;
touchTurnUp = yd / -100f;
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) {
if (fb != null) {
fb.dispose();
}
fb = new FrameBuffer(gl, w, h);

if (master == null) {

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

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

ZipInputStream zis;
try {
zis = new ZipInputStream(getResources().getAssets().open("car.zip"));
zis.getNextEntry();
} catch (IOException e) {
throw new RuntimeException(e);
}

parts = Loader.loadSerializedObjectArray(zis);
world.addObjects(parts);
world.buildAllObjects();

for (Object3D obj : parts) {

if (obj.getName().indexOf("windshield") != -1 || obj.getName().indexOf("glas") != -1 || obj.getName().indexOf("lights_fro") != -1) {
obj.setTransparency(3);
}

obj.strip();
if (obj != parts[0]) {
obj.addParent(parts[0]);
}
}

Camera cam = world.getCamera();
cam.moveCamera(Camera.CAMERA_MOVEOUT, 250);
cam.lookAt(parts[0].getTransformedCenter());

SimpleVector sv = new SimpleVector();
sv.set(parts[0].getTransformedCenter());
sv.y -= 500;
sun.setPosition(sv);
sun.setDiscardDistance(-1);
MemoryHelper.compact();

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

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

public void onDrawFrame(GL10 gl) {
if (touchTurn != 0) {
parts[0].rotateY(touchTurn);
touchTurn = 0;
}

if (touchTurnUp != 0) {
parts[0].rotateX(touchTurnUp);
touchTurnUp = 0;
}

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++;
}
}
}


While running, it uses around 10mb and renders @ 20fps on a Galaxy S class device. It looks like this:


MrAdam

Thanks a lot. It works perfectly when keeping the model split up :-)
It even runs on old phones like the HTC Wildfire now.

Anyways, I got one more question. As the loader takes the materials from the 3DS file, is it possible to fetch the material in the code and change the ambient color?

Regards, MrAdam.

P.S. Sorry for the late response, I was in a bike accident last thursday ^^

EgonOlsen

Not directly, but you could try to hack around it. The colors are represented by little colored textures. These texture have generated names that might appear in the log output when they get added to the TextureManager. If you replace them in the TextureManager after the loading has finished, the color's should change.

MrAdam

Got it working with swapping the textures, thanks.

Another question: Is it possible to make the windows semi-transparent, to make it looked like toned windows?

And another question: I've noticed that the model looks a hell of a lot smoother on a Galaxy S II and a Galaxy Nexus, than it does on a Desire, Desire S or Wildfire S. Is there any way to make the edges less "crispy" on low dpi devices? Anti-aliasing maybe?

Regards, MrAdam.

Thomas.

#7
Anti-aliasing is possible to use in OGL2 only. I'm using this.

switch (GameConfig.oglVersion) {
case 1:
mGLView.setEGLConfigChooser(new GLSurfaceView.EGLConfigChooser() {
@Override
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
int[] attributes = new int[] { EGL10.EGL_DEPTH_SIZE, 24, EGL10.EGL_NONE };
EGLConfig[] configs = new EGLConfig[1];
int[] result = new int[1];
egl.eglChooseConfig(display, attributes, configs, 1, result);
return configs[0];
}
});
break;
case 2:
mGLView.setEGLContextClientVersion(2);
if (GameConfig.antiAliasing)
mGLView.setEGLConfigChooser(new AAConfigChooser(mGLView));
break;
}


edit: But with to large count of edges, it'll be probably very slow.

MrAdam

Quote from: Thomas. on May 10, 2012, 03:14:56 PM
Anti-aliasing is possible to use in OGL2 only. I'm using this.

switch (GameConfig.oglVersion) {
case 1:
mGLView.setEGLConfigChooser(new GLSurfaceView.EGLConfigChooser() {
@Override
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
int[] attributes = new int[] { EGL10.EGL_DEPTH_SIZE, 24, EGL10.EGL_NONE };
EGLConfig[] configs = new EGLConfig[1];
int[] result = new int[1];
egl.eglChooseConfig(display, attributes, configs, 1, result);
return configs[0];
}
});
break;
case 2:
mGLView.setEGLContextClientVersion(2);
if (GameConfig.antiAliasing)
mGLView.setEGLConfigChooser(new AAConfigChooser(mGLView));
break;
}


edit: But with to large count of edges, it'll be probably very slow.
I already tried that. Even though I got GLES2.0 enabled I get this:
05-10 16:01:19.255: E/libEGL(24536): called unimplemented OpenGL ES API
05-10 16:01:19.263: E/libEGL(24536): called unimplemented OpenGL ES API
05-10 16:01:19.263: E/libEGL(24536): called unimplemented OpenGL ES API
05-10 16:01:19.263: E/libEGL(24536): called unimplemented OpenGL ES API
05-10 16:01:19.263: E/libEGL(24536): called unimplemented OpenGL ES API
05-10 16:01:19.263: E/libEGL(24536): called unimplemented OpenGL ES API
05-10 16:01:20.419: E/libEGL(24536): called unimplemented OpenGL ES API
05-10 16:01:20.419: E/libEGL(24536): called unimplemented OpenGL ES API
05-10 16:01:20.419: E/libEGL(24536): called unimplemented OpenGL ES API
05-10 16:01:20.544: E/AndroidRuntime(24536): FATAL EXCEPTION: GLThread 873
05-10 16:01:20.544: E/AndroidRuntime(24536): java.lang.ArrayIndexOutOfBoundsException: length=0; index=0
05-10 16:01:20.544: E/AndroidRuntime(24536): at com.threed.jpct.CompiledInstance.fill(CompiledInstance.java:1155)
05-10 16:01:20.544: E/AndroidRuntime(24536): at com.threed.jpct.Object3DCompiler.compile(Object3DCompiler.java:145)
05-10 16:01:20.544: E/AndroidRuntime(24536): at com.threed.jpct.World.compile(World.java:1914)
05-10 16:01:20.544: E/AndroidRuntime(24536): at com.threed.jpct.World.renderScene(World.java:1038)
05-10 16:01:20.544: E/AndroidRuntime(24536): at nodes.android.MainActivity$GLRenderer.onDrawFrame(MainActivity.java:202)
05-10 16:01:20.544: E/AndroidRuntime(24536): at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1462)
05-10 16:01:20.544: E/AndroidRuntime(24536): at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1216)

Thomas.

You need to enable OpenGL ES 2.0 in your app through AndroidManifest.xml.

<uses-feature android:glEsVersion="0x00020000"></uses-feature>
<uses-sdk android:targetSdkVersion="8" android:minSdkVersion="8"></uses-sdk>

MrAdam

Quote from: Thomas. on May 10, 2012, 04:21:41 PM
You need to enable OpenGL ES 2.0 in your app through AndroidManifest.xml.

<uses-feature android:glEsVersion="0x00020000"></uses-feature>
<uses-sdk android:targetSdkVersion="8" android:minSdkVersion="8"></uses-sdk>

Still getting
05-10 16:26:49.234: E/AndroidRuntime(1331): FATAL EXCEPTION: GLThread 156
05-10 16:26:49.234: E/AndroidRuntime(1331): java.lang.ArrayIndexOutOfBoundsException: length=0; index=0
05-10 16:26:49.234: E/AndroidRuntime(1331): at com.threed.jpct.CompiledInstance.fill(CompiledInstance.java:1155)
05-10 16:26:49.234: E/AndroidRuntime(1331): at com.threed.jpct.Object3DCompiler.compile(Object3DCompiler.java:145)
05-10 16:26:49.234: E/AndroidRuntime(1331): at com.threed.jpct.World.compile(World.java:1914)
05-10 16:26:49.234: E/AndroidRuntime(1331): at com.threed.jpct.World.renderScene(World.java:1038)
05-10 16:26:49.234: E/AndroidRuntime(1331): at nodes.android.MainActivity$GLRenderer.onDrawFrame(MainActivity.java:246)
05-10 16:26:49.234: E/AndroidRuntime(1331): at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1462)
05-10 16:26:49.234: E/AndroidRuntime(1331): at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1216)



Thomas.

So, I have no idea, where is problem... :D

EgonOlsen

To use 2.0, you have to use the other constructor for your FrameBuffer....the one without the gl instance.