Applet & memory concerns

Started by nouknouk, October 20, 2008, 02:45:55 PM

Previous topic - Next topic

nouknouk

Hi,

I'm just a new user of JPCT: i'm currently evaluating the possibility to use JPCT to make 3D applet games.

For the moment, i just did a really basic try: instanciating something like 50 objects from a 3ds file:

                Object3D[] objects = Loader.load3DS("pesanthouse.3ds", 0.03f);
                for (int i=0; i<7; ++i) {
                    for (int j=0; j<7; ++j) {
                        Object3D pivot = Object3D.createDummyObj();
                        for (Object3D o2 : objects) {
                            Object3D o = o2.cloneObject();
                            o.setTexture("house");
                            o.getMesh().compress();
                            o.build();
                            pivot.addChild(o);
                            world.addObject(o);
                        }
                        pivot.translate(i*8.f, j*8.f, 0.f);
                        pivot.build();
                        world.addObject(pivot);
                    }
                }



The 3ds object looks reasonable in size, with somehting like 1000 polygons and the texture is about 1MB:
Quote
Loading file pesanthouse.3ds
File pesanthouse.3ds loaded...51691 bytes
Processing new material 01 - Default!
Texture named ROOF.JPG added to TextureManager!
Processing new material 02 - Default [Bo!
Processing object from 3DS-file: Box01
Object 'Box01_jPCT2500' created using 124 polygons and 64 vertices.
Processing object from 3DS-file: Box02
Object 'Box02_jPCT2501' created using 124 polygons and 64 vertices.
Processing object from 3DS-file: Box04
Object 'Box04_jPCT2502' created using 762 polygons and 532 vertices.

I'm afraid when i launch this code (as an application, even not yet as an applet), because I get an OutOfMemoryError.

As I know applets are even more constraining than application (64MB of max memory if I remember well)
That's why, I have more and more doubts about the possibility to use JPCT in my applet.

My goal is to make some kind of '3D top view' (something like that). Thus, 50 objects like my house will be something quickly reachable.

Here's my question: do you see any problem inside my code, or any reason that makes my code use more memory than needed ?

Many thanks in advance.

Melssj5

for (Object3D o2 : objects) ????? I have never seen this for before, anyway.


Well, the loader3DS method loads a 3ds file into an array of Objects3D, this is becausse of the file format. each part can have a different texture inside the whole model. When you load a 3ds file into jpct you usually merge all that parts into one. and then you will have the  model loaded into an Object3D. as I see, you are taking all the subparts of the model, asigning the 1MB texture to each part and building them many before adding to the world. You should add it first and after that build it. check about the testure becausse you are applying the whole texture to each part of the 3ds file.
Nada por ahora

EgonOlsen

Applets are limited to 64mb. The new plugin in Java6u10 lets you specify -Xmx like an application does but it works only in Firefox3 and IE7. That aside, you may try to set Config.saveMemory=true; at the start of your application (before instantiating any jPCT object). What size is the texture in pixels? I assume it's 512*512?


EgonOlsen

Quote from: Melssj5 on October 20, 2008, 06:45:09 PM
for (Object3D o2 : objects) ????? I have never seen this for before, anyway.
It's Java5's enhanced for loop. Very handy once you got used to it.

Quote from: Melssj5 on October 20, 2008, 06:45:09 PM
When you load a 3ds file into jpct you usually merge all that parts into one. and then you will have the  model loaded into an Object3D.
Correct, that's another thing to consider. If possible, merge the parts of your loaded object into one object and clone that. It will remove some overhead. In addition, try to call yourObj.getMesh().compress() and that blueprint to squeeze out some extra bytes.

nouknouk

#4
Thanks for those details, but:

- I already applied optimizations (Config.saveMemory=true ; yourObj.getMesh().compress() ), but the memory error reamains the same.
- the 3ds file is a house composed of only 3 parts (3 sub-objects).
- it's difficult to merge parts as sub-objects are using different textures (roof.jpg and house_texture.jpg). The big texture is a 1024x1024 one, and is about 1MB in JPEG format.
- even when i load the mesh without applying and loading any texture, i still face the memory error.

You'll find all resources (3ds file, java code & textures) here

I added a memory log in the loop code:

System.out.println("memory used: "+((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/1024/1024)+"MB.");


At first iteration, i get 16MB of memory used. In a few iterations, it reaches 64MB and throws the exception. Any object loaded seem to use over 1MB of memory...

I'm quite surprised a simple application with - say - fifty objects of 1000 polygons takes more than 64MB in memory.
Isn't it a pitfall in my code ?

Indeed, when I see screenshots of other jpct apps (Robombs, technopolies, ...), i suppose there are lot more objects loaded in memory, and much more polygons in the world....


Melssj5

You dont have to apply the textures to the objects manually and merginh them doesnt mean using the same texture. If the 3ds file has the information about the textures to use, u just have to add the textures to the texture manager and they will be loaded on the model automatically.

dont build the object until adding it to the world.
Nada por ahora

EgonOlsen

Your example code loads creates a new set of objects in each iteration, because it loads them over and over again. By applying something like you've posted above, you get:


Object3D[] objs=Loader.load3DS("pesanthouse.3ds", 0.01f);
           
               
    for (int i=0; i<10; ++i) {
             for (int j=0; j<10; ++j) {
                     for (Object3D o : objs) {
                            Object3D o2=o.cloneObject();
                            o2.translate(i*7.f, j*7.f, 0.f);
                            world.addObject(o2);
                            System.out.println((i*10+j)+"/ memory used: "+((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/1024/1024)+"MB.");
                      }
              }
      }
      objs=null;

This is better, but still doesn't work with 100 objects within 64mb. What works much better, is merging:


                Object3D[] objs=Loader.load3DS("pesanthouse.3ds", 0.01f);
                Object3D house=Object3D.createDummyObj();
                for (Object3D o : objs) {
                house=Object3D.mergeObjects(house, o);
                }
               
                for (int i=0; i<10; ++i) {
                    for (int j=0; j<10; ++j) {
                           Object3D o2=house.cloneObject();
                            o2.translate(i*7.f, j*7.f, 0.f);
                            world.addObject(o2);
                            System.out.println((i*10+j)+"/ memory used: "+((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/1024/1024)+"MB.");
                    }
                }
                objs=null;


I'm at 37mb after this. Like Melssj5 said, texture assignment shouldn't be the problem. The loader does this or you can always do it in the merge loop. If all that isn't an option for whatever reason, reduce the polygon count or otherwise it won't fit in the applet. Technopolies isn't an applet but there are other applet examples in the projects section, that show that applets and jPCT do work fine. I agree that the memory usage is quite high...i would love to reduce it, but if you dig deeper into this forum, you'll see that i've already done alot in this regard. There is not much more to squeeze out of Object3D.
However, i may look at it again within the next weeks...

EgonOlsen

After taking a quick look, i think that there is at least one place left, where memory consumption of Object3D's can be further reduced. I'll look into it...

nouknouk

Your tip works well, as you mentionned.

I'm still a bit surprised that for exactly the same amount of 'relevant' data, there is a difference of memory consumption as high as 30MB, just beacuse the data is spread into 300 objects instead of 100.

Anyway, many many thanks for your support, and your concern  :)

paulscode

Another way to reduce memory is to become a low-poly-nazi.  Spend a lot of time looking at every little part of each model to see where polys can be combined or cut.  Really good texture can sometimes allow you to reduced polys down even further.  The other thing is to consider how closely an object will be viewed from, or how much it will be focused on in the scene.  You can often get away with much fewer polys on objects that the player doesn't really look at or which are rarely ever very close to the camera.

EgonOlsen

#10
Quote from: nouknouk on October 20, 2008, 10:01:26 PM
I'm still a bit surprised that for exactly the same amount of 'relevant' data, there is a difference of memory consumption as high as 30MB, just beacuse the data is spread into 300 objects instead of 100.
Me too. I think that this is a flaw in the 3ds-loader. Maybe you can convert your model to OBJ-format and see if that uses less memory when loaded!? I'll fix this flaw for the next release.

nouknouk

#11
Quote from: EgonOlsen on October 21, 2008, 08:40:59 AM
I think that this is a flaw in the 3ds-loader.

I did a simple test: loading 1000 times the same 3DS file:
Quote
                for (int i=0; i<1000; ++i) {
                    Object3D[] objects = Loader.load3DS("pesanthouse.3ds", 0.03f);
                    objects = null;
                    System.gc(); System.gc(); System.gc(); System.gc();
                    System.out.println("["+i+"] mem usage:"+((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/1024/1024)+"MB." );
                }

But even after 1000 iterations, the memory consumed remains to 0MB.
So I think it's more closely related to the content of the Object3D class.

I did some other benchs by watching the memory consumption when instanciating lot's of objects with a various amount of triangles:

// the getPlane 'quad' parameter is the value I change
// note calling obj.build() and world.addObject(...)
// has no effect on memory consumption.

                Object3D[] objs = new Object3D[1000];
                for (int i=0; i<1000; ++i) {
                    objs[i] = Primitives.getPlane(10, 1.f);
                    objs[i].build();
                    System.gc(); System.gc(); System.gc(); System.gc();
                    System.out.println("["+i+"] mem usage:"+((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/1024/1024)+"MB." );
                    world.addObject(objs[i]);
                }


Here are the results:


getPlane(2, 1.f); => 8 triangles / object
=> 126 objects / MB
=> 1008 triangles / MB

getPlane(6, 1.f); => 72 triangles / object
=> 66 objects  / MB
=> 2376 triangles / MB

getPlane(8, 1.f); => 128 triangles / object
=> 18 objects  / MB
=> 2304 tirangles / MB


getPlane(10, 1.f); => 200 triangles / object
=> 13 objects  / MB
=> 2600 triangles / MB

getPlane(30, 1.f); => 1800 triangles / object
=> 1.5 objects / MB
=> 2700 triangles / MB

getPlane(60, 1.f); => 7200 triangles / object
=> 55 MB for 21 objects
=> 0.382 object / MB
=> 2750 triangles / MB

getPlane(120, 1.f); => 28800 triangles / object
=> 44 MB for 4 objects
=> 0.0909 object / MB
=> 2617 triangles / MB


Let's dicuss about the results:

- the average memory consumption is around 2700 triangles per megabyte comsumed (I don't rely on results when quad<8, which is not representative, as the memory consumed by the class itself has a big impact). This leads to a memory consumption of 388 bytes for each triangle (to give an idea it's the amount of place taken by 97 float values, or 32 vectors).

note: the results are in line with the memory consumption of my 3DS file (after merging) : 37MB for almost 100k triangles.

=> This looks really high.

In Object3D API, it seems that all information needed by a trinagle is:

public int addTriangle(SimpleVector vert1, float u,float v,
                       SimpleVector vert2,float u2,float v2,
                       SimpleVector vert3,float u3,float v3,
                       int textureID, int sec)

Which is something like 17 * 4 bytes = 68 bytes of 'relevant' data (to be compared with the 388 bytes found above).

Thus i suppose this data may be stored internally inside one or several object instances (<= I mean 'java object' here, like SimpleVector(s) or any internal 'Triangle' class).

Maybe that's the main cause of the overhead.

Another point: i discovered the ID of the sector is stored for each triangle. Wouldn't it be more efficient to only store the sector's ID for a whole object instead ?


EgonOlsen

Quote from: nouknouk on October 21, 2008, 10:51:21 AM
But even after 1000 iterations, the memory consumed remains to 0MB.
So I think it's more closely related to the content of the Object3D class.
That's not was i was talking about. The 3ds loader is flawed in a way that it reserves too much memory for a loaded object. It's not that the loader itself has a memory leak or something.

Quote from: nouknouk on October 21, 2008, 10:51:21 AM
In Object3D API, it seems that all information needed by a trinagle is:

public int addTriangle(SimpleVector vert1, float u,float v,
                       SimpleVector vert2,float u2,float v2,
                       SimpleVector vert3,float u3,float v3,
                       int textureID, int sec)

Which is something like 17 * 4 bytes = 68 bytes of 'relevant' data (to be compared with the 388 bytes found above).

Thus i suppose this data may be stored internally inside one or several object instances (<= I mean 'java object' here, like SimpleVector(s) or any internal 'Triangle' class).
There's a lot more to be stored then this. The normals are missing, there's a back buffer for normalized texture coordinates, because the hardware renderer needs it as well as some runtine generated information.


Quote from: nouknouk on October 21, 2008, 10:51:21 AM
Another point: i discovered the ID of the sector is stored for each triangle. Wouldn't it be more efficient to only store the sector's ID for a whole object instead ?
No, because an object can cover multiple sectors. However, no one uses portal rendering in jPCT, so it would be save to drop this array if it's not used. But that requires a lot more of refactoring than i'm planning to do before the next version comes out.

nouknouk

ok, thanks for all those explanations.

I would have just another 'off topic' question: why is jpct released as free for 'all usages' (commercial or not) but source code isn't published ? (at all, not a judgment here, I just would like to better understand your approach).

Melssj5

That question has been asked before and the answer was "Becausse I want it on that way".  8)
Nada por ahora