LWJGL Forum

Programming => Lightweight Java Gaming Library => Topic started by: Skippy0 on January 25, 2005, 12:44:07

Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: Skippy0 on January 25, 2005, 12:44:07
I've been using MipMap.gluBuild2DMipmaps() to create texture-mipmaps.

When using huge textures I began to run into trouble:
- texture size: 4096*4096 RGBA (64MB raw)

I expected RAM-usage to expode here, when the mipmaps were being generated, and indeed it did: ~200MB.
That's not so bad, if it would released too, but it's not!

Loading the same texture without mipmap-ing makes the java-process consume ~20MB ram, as the texture is stored in vRAM. With mipmapping, the ram-usage of the java-process stays at >240MB, swapping to page-file initially, increasing the startup-time by a few minutes (!).

So why is MipMap.gluBuild2DMipmaps() not releasing it's buffers? (or: what am I missing?)

Specs:
- WinXP, ATi Radeon 9700pro, 512RAM, P2.4GHz, JRE 1.5.0_00
Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: Skippy0 on January 25, 2005, 12:59:26
I narrowed it down a bit:

It only appears to be happening when a GLError occurs inside MipMap.gluBuild2DMipmaps()
(if it doesn't support 4096^2 textures)

Even if this is the case, that shouldn't prevent it from releasing the buffers.
Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: Matzon on January 25, 2005, 18:00:53
are you nulling the (invalid) buffer afterwards? - else it won't be reclaimed
Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: Skippy0 on January 25, 2005, 18:13:22
Yes.

A 64MB buffer can't take 200MB ram anyway. I suspect there are new buffers being created and not properly nullified on error in the mipmap-method.
Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: tomb on January 25, 2005, 21:50:46
Have you looked at the gluBuild2DMipmaps() source? It's in the src\java\org\lwjgl\opengl\glu\MipMap.java file in the source package that can be downloaded from sourceforge with the rest of lwjgl.

I've had a quick look and can't see anything wrong. The buffers are created inside the function and no referance is given away as I can tell. Although it can be optimized to reduce the number of buffers that are created. A new one don't have to be created at every mipmap level. It can instead reuse the previous one.

Have you tried a System.gc() to make sure a full gc is performed? Have you tried on a different computer to rule out driver bugs?
Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: princec on January 25, 2005, 22:21:21
Buffers work differently in Java. They are allocated on the heap, totally bypassing the GC and never triggering collection - only the stub Java object is held on the Java heap.

All well and good.

However, they are not disposed immediately as they have a finalizer. This means that they are collected on a whim at an indeterminate point in the future.

The end result is.... this is not a good way to do it. We'd better find an alternative.

Cas :)
Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: tomb on January 26, 2005, 00:04:05
Quote from: "princec"Buffers work differently in Java. They are allocated on the heap, totally bypassing the GC and never triggering collection - only the stub Java object is held on the Java heap.

All well and good.

However, they are not disposed immediately as they have a finalizer. This means that they are collected on a whim at an indeterminate point in the future.

The end result is.... this is not a good way to do it. We'd better find an alternative.

Cas :)

So native buffers are not collected in the same way as the rest of the garbage? I assumed that it was part of the gc algo, and that it would be collected if a full gc was performed, like when you call System.gc().
Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: Matzon on January 26, 2005, 06:31:38
I think that Cas is saying that they behave like any other object in Java - that is, it is garbage collected when the GC'er determins to do so. For instance when you suggest a garbage collection via System.gc();

So you have to "wait" for the garbage collection when you're done with a large bytebuffer (which can be anything from miliseconds to seconds). A better way would be to be able to call Buffer.dispose() or something like that, so you're sure that is deleted reight now.
Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: spasi on January 26, 2005, 08:56:40
I think this situation is worse with direct buffers. Java buffers are plain java arrays, normally collected as usual, but maybe collecting memory outside the Java heap, for direct ones, is handled differently.

Anyway, tomb is right, that method can be optimized. I'll fix it one of these days (if noone else does in the meantime).
Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: Matzon on January 26, 2005, 10:28:50
trying to see if there is anything we can do to force a dispose - but I can't find anything usefull...

A buffer is implemented in j2se/src/share/classes/java/nio/Direct-X-Buffer.java. It does not implement finalize and all reclaiming is done using sun.misc.Cleaner (PhantomReference) and sun.misc.Unsafe. However it all seems to be run automatically - thats is, I can't find any method (public or private) for activating the cleaning sequence of the Buffer... Will investigate more, later
Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: princec on January 26, 2005, 10:58:11
Spasi - yes, that's exactly what I'm trying to say. The memory is allocated outside the heap and is only cleared by a finalizer. This is unfortunate because it may take a long time before the GC decides to clear it, and it can easily cause the machine to start swapping.

<edit>Actually Matzon's got it right but the mechanism is roughly the same - you've got to wait for the reference queue to get around to noticing the buffer is a phantom. Which may be never.

Cas :)
Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: spasi on January 26, 2005, 23:08:08
I commited a change with which at most 2 buffers are allocated, for all levels. But...

I highly recommend that noone uses at runtime this evil method!

Especially for so large textures that Skippy uses. It may be ok as an off-line preprocess, but if there's absolutely a need for runtime mipmap generation one should either implement custom image scaling or use a GL extension to do it (SGIS_generate_mipmap is widely supported).

Some details:

1) Because this is a public interface, the input buffer cannot be modified. In a custom implementation one would certainly reuse it after sending the first level to GL. One buffer less.

2) gluScaleImage allocates two float arrays (!), one for input and one for output, for each level! Highly inefficient and I'm pretty sure that's where Skippy's 200mb came from.

3) The implementation is generally a not so good port of the C equivalent (e.g. the buffer set/get with explicit offsets in gluScaleImage).
Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: Skippy0 on January 26, 2005, 23:37:07
To comment on your reply, spasi:

I've already changed it, it's using 16 1024x1024 textures now, better to cull too :wink:

I don't know whether or not reducing the allocated buffers will be much faster, as I suspect the actual scaling to be 99% of the cpu-cycles. RAM usage could be a problem on low-end systems.
Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: spasi on January 27, 2005, 12:09:23
Quote from: "Skippy0"I don't know whether or not reducing the allocated buffers will be much faster, as I suspect the actual scaling to be 99% of the cpu-cycles. RAM usage could be a problem on low-end systems.

Yeah, speed should not be too much of problem, it's done only once at load-time anyway, the point is that ram usage is just too wasteful. Everything should be garbage collected immediately of course, but you can't be sure how it would affect load time and how the OS would handle your app.

Please, have a look at the two methods (gluBuild2DMipmaps and gluScaleImage that is used by it) and calculate the amount of memory allocated for each image, for each mip level. The total for 16 1K images is enormous!

Now that I think about it, GLU should be moved to the util package, to make it more obvious that it shouldn't be used for production purposes. What do others say?
Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: tomb on January 27, 2005, 14:34:32
GLU has to stay as part of the core. Any performance problems can be noted in the javadoc.

Btw, I'm using gluBuild2DMipmaps in Squareheads and I haven't noticed any huge performance problems. But I'm creating so much garbage at loading it's hard to tell where it's all coming from :)
Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: tomb on February 10, 2005, 02:27:49
Rewrote the gluBuild2DMipmaps in Squareheads to cache the buffers used when scaling the images. Mesuring the function with the highres time I found that the cached version was 30% faster. Probably a bit more since I could not mesure the benefit of less garbage collecting. The Overall loading time was reduced by 10%, from 10 to 9 seconds.
Title: gluBuild2DMipmaps() mem-leak on GLError
Post by: princec on February 10, 2005, 11:02:11
I think the GLU methods should really do their work using byte[] arrays.

Cas :)