I want to build a SimpleApplication to show OpenStreetMap tiles.
I use a separate thread to read tile image from url , make Geometry and put Geometry in a list.
Then rootNode attachChild in simpleUpdate.
It is worked fine on desktop.
But on android, main thread is always freezed a moment when attachChild.
There are some codes. Thanks.
[java]
public class WorkerThread implements Runnable {
private int x;
private int y;
public WorkerThread(int x, int y){
this.x = x;
this.y = y;
}
int d = 0;
@Override
public void run() {
try {
String name = x + "/" + y + "/" + zoom;
Quad b = new Quad(TILE_SIZE, TILE_SIZE);
Geometry geom = new Geometry(name, b);
geom.setLocalTranslation(new Vector3f(x, y, -1));
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setTexture("ColorMap", tileServer.loadTexture(x, y, zoom));
mat.setColor("Color", ColorRGBA.White);
geom.setMaterial(mat);
synchronized (tiles) {
tiles.addLast(geom);
}
tileloaded.add(name);
tileloading.remove(name);
} catch (Exception e) {
//log.log(Level.SEVERE, "failed to load url \"" + url + "\"", e);
e.printStackTrace();
}
}
}
public void simpleUpdate(float tpf) {
synchronized (tiles) {
while (!tiles.isEmpty()) {
Geometry geom = tiles.pollLast();
Vector3f v3 = geom.getLocalTranslation();
int dy = (int)(v3.y * TILE_SIZE + TILE_SIZE);
int dx = (int)(v3.x * TILE_SIZE);
geom.setLocalTranslation(new Vector3f(dx, -dy, -1));
rootNode.attachChild(geom);
}
}
}
In modern Java, if you find yourself using the synchronized keyword for something like this then you are doing it wrong. It’s just that simple. I don’t mean to be harsh but threading is such a tricky thing to get right in the first place that just blindly treading in without looking around will lead to nothing but trouble. Not only that… synchronized is relatively expensive. It’s the heavy-sledgehammer of the threading world.
Familiarize yourself with the java.util.concurrent classes. When you want to do concurrent stuff (ie: threading) they are the way to go. In this case, ConcurrentLinkedQueue is probably what you want but hopefully you’ll look around and do some tutorials before just cutting and pasting that in.
In modern Java, if you find yourself using the synchronized keyword for something like this then you are doing it wrong. It’s just that simple. I don’t mean to be harsh but threading is such a tricky thing to get right in the first place that just blindly treading in without looking around will lead to nothing but trouble. Not only that… synchronized is relatively expensive. It’s the heavy-sledgehammer of the threading world.
Familiarize yourself with the java.util.concurrent classes. When you want to do concurrent stuff (ie: threading) they are the way to go. In this case, ConcurrentLinkedQueue is probably what you want but hopefully you’ll look around and do some tutorials before just cutting and pasting that in.
Also, if you add a bunch of spatials to the root node in one pass it’s going to be slow. Even one really huge spatial could lag but if you are adding a bunch then it will definitely lag. Do only one per frame and see how that goes.
…and stop using synchronized and use ConcurrentLinkedQueue as in my other two posts.
By the way, use ConcurrentLinkedQueue… and if you haven’t seen it yet: use ConcurrentLinkedQueue.
Thanks pspeed. I have use ConcurrentLinkedQueue instead of list.
And update simpleUpdate like this:
[java]
public void simpleUpdate(float tpf) {
Geometry geom = tiles.poll();
if (geom == null)
return;
Vector3f v3 = geom.getLocalTranslation();
int dy = (int)(v3.y * TILE_SIZE + TILE_SIZE);
int dx = (int)(v3.x * TILE_SIZE);
geom.setLocalTranslation(new Vector3f(dx, -dy, -1));
rootNode.attachChild(geom);
}
[/java]
But when one tile appeared on the screen, my drag still freezed a moment.
I confused that why it only happend on android.
I have add “android:hardwareAccelerated=“true”” in AndroidManifest.xml.
Is there anything I can do to raise jmonkeyengine’s efficiency on android?
…completely missed that this was on android. Sorry. It could be that given the complexity of your tiles that there will always be a frame hiccup. When the tile first appears, all of its mesh data has to be sent to the GPU which will be dependent on how much there is.
Sorry. Just saw this thread (no pun intended ). Is your geometry created ahead of time and you just need to attach it? If so, try preloading the spatial by either using renderManager.preloadScene(spatial) or material.preload() this is from memory, so you’ll need to check the syntax.
The purpose of these is to preload the data to the gpu so that it doesn’t need to be done on the first render. The only difference between them is that the first one recursively loads all the children if any exist so you can preload a Node which will preload all the children automatically.
If this is your issue, do the preloading at a time where you can hide the hesitation like during a “loading, please wait” menu or during startup.
This commonly needed for android to hide the texture loading, compression, and sending it to the gpu time.
Thanks everyone.
I have found the solution.
If I add following code in MainActivity, there is no hiccup at all. Although I don’t know why.
[java]
TextureUtil.ENABLE_COMPRESSION = false;
[/java]
And I’d like to ask iwgeric a few questions:
Where should I call “material.preload(RenderManager)”? In simpleRender function?
should I call “material.preload(RenderManager)” in other thread but not render thread?
@coordinate said:
Thanks everyone.
I have found the solution.
If I add following code in MainActivity, there is no hiccup at all. Although I don't know why.
[java]
TextureUtil.ENABLE_COMPRESSION = false;
[/java]
And I’d like to ask iwgeric a few questions:
Where should I call “material.preload(RenderManager)”? In simpleRender function?
should I call “material.preload(RenderManager)” in other thread but not render thread?
Usually in init or update… but it has to be called from the render thread wherever you call it. Which is why it was suggested to do it when the user will not notice the frame hiccup because you would still get one… you can just control when it happens.
@coordinate said:
Thanks everyone.
I have found the solution.
If I add following code in MainActivity, there is no hiccup at all. Although I don't know why.
[java]
TextureUtil.ENABLE_COMPRESSION = false;
[/java]
And I’d like to ask iwgeric a few questions:
Where should I call “material.preload(RenderManager)”? In simpleRender function?
should I call “material.preload(RenderManager)” in other thread but not render thread?
If setting ENABLE_COMPRESSION=false solved your issue, then what I posted last is the cause. For android, when a texture is loaded, an Android Bitmap is created in memory from the texture file. The first time the texture is rendered, the android renderer checks to see if the texture needs to be sent to the GPU. If it does and it matches the “can compress” criteria, the android bitmap is compressed using ETC1 before being sent to the GPU.
Part of the criteria involves the setting of the static member TextureUtil.ENABLE_COMPRESSION as well as whether or not the texture has alpha (ETC1 doesn’t support textures with alpha). The compression is nice because from what I’ve seen, it reduces the size of the image being sent to the GPU quite a bit which allows for more textures to be used before getting the Out of Memory errors.
From a design point of view an Android, it would be best to create the application so that the geometries can be created and preloaded so that all the work of compressing and sending to the GPU is done during app startup or scene loading. This way you don’t see (or minimize seeing) hesitations during game play when attaching spatials to the scene graph.
I typically will load a j3o and then call renderManager.preloadScene(spatial from j3o load) during app startup for all my scene objects and then just attach them when I need to during game play. However, creating the geometry from code and then calling material.preload(renderManager) will serve the same purpose. Because I have a splash screen or a “loading” screen up during this time, the hesitation is hidden from the user.
<cite>@iwgeric said:</cite>
If setting ENABLE_COMPRESSION=false solved your issue, then what I posted last is the cause. For android, when a texture is loaded, an Android Bitmap is created in memory from the texture file. The first time the texture is rendered, the android renderer checks to see if the texture needs to be sent to the GPU. If it does and it matches the "can compress" criteria, the android bitmap is compressed using ETC1 before being sent to the GPU.
Part of the criteria involves the setting of the static member TextureUtil.ENABLE_COMPRESSION as well as whether or not the texture has alpha (ETC1 doesn’t support textures with alpha). The compression is nice because from what I’ve seen, it reduces the size of the image being sent to the GPU quite a bit which allows for more textures to be used before getting the Out of Memory errors.
From a design point of view an Android, it would be best to create the application so that the geometries can be created and preloaded so that all the work of compressing and sending to the GPU is done during app startup or scene loading. This way you don’t see (or minimize seeing) hesitations during game play when attaching spatials to the scene graph.
I typically will load a j3o and then call renderManager.preloadScene(spatial from j3o load) during app startup for all my scene objects and then just attach them when I need to during game play. However, creating the geometry from code and then calling material.preload(renderManager) will serve the same purpose. Because I have a splash screen or a “loading” screen up during this time, the hesitation is hidden from the user.
This is solid advice for desktop as well. It’s a good practice to get into for meshes/materials/textures/etc. Make sure you consider how you are cloning as well. If you can share Geometries, materials, etc… do so.