Bug in glu.gluBuild2DMipmaps

Hi!

I have found a bug in gluBuild2DMipmaps it crashes with some textures I have generated 1067
x 4000. It works fine with another texture: 192 x 4500 tho.
at com.sun.opengl.impl.mipmap.ScaleInternal.scale_internal_ubyte(ScaleInternal.java:253)
at com.sun.opengl.impl.mipmap.BuildMipmap.gluBuild2DMipmapLevelsCore(BuildMipmap.java:535)
at com.sun.opengl.impl.mipmap.Mipmap.gluBuild2DMipmaps(Mipmap.java:762)
at javax.media.opengl.glu.GLU.gluBuild2DMipmapsJava(GLU.java:1525)
at javax.media.opengl.glu.GLU.gluBuild2DMipmaps(GLU.java:1581)

Looking in the database I found a bug (177) that gave a similar error. It seems to have been fixed but it is not. I have tried the latest build available.

If there is a workaround apart from using power of 2 textures pls let me know

If you specify -Djogl.glu.nojava on the command line does this work around the problem?

Can you make your image and a small test case available?

Hi,

I have cannot really give you a simple case, I dont build textures based on images but on data. From each data value I assign a color and build the texture from it.

I have tried with the option -Djogl.glu.nojava but it crashed with log:

An unexpected error has been detected by HotSpot Virtual Machine:

EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x68b27372, pid=5464, tid=1724

Java VM: Java HotSpot™ Client VM (1.5.0_06-b05 mixed mode, sharing)

Problematic frame:

C [GLU32.dll+0x7372]

--------------- T H R E A D ---------------

Current thread (0x02cd7428): JavaThread “AWT-EventQueue-0” [_thread_in_native, id=1724]

siginfo: ExceptionCode=0xc0000005, reading address 0x03db0000

Registers:
EAX=0x00001401, EBX=0x0c09c8b0, ECX=0x00001401, EDX=0x00000001
ESP=0x0362f744, EBP=0x0362f75c, ESI=0x03daf3a0, EDI=0x03db0000
EIP=0x68b27372, EFLAGS=0x00010246

Top of Stack: (sp=0x0362f744)
0x0362f744: 5ed04ba4 000003e9 00001403 00000001
0x0362f754: 00000000 00000c81 0362f7dc 68b27bb7
0x0362f764: 0000042b 000003e9 00000c84 00001401
0x0362f774: 00000000 00000021 00000001 02cd7428
0x0362f784: 26ba40b8 26ba40b8 00000004 00000000
0x0362f794: 00000000 00000000 00000000 00000000
0x0362f7a4: 00000004 00000000 00000000 00000000
0x0362f7b4: 00000000 00000000 00000400 000003e9

Instructions: (pc=0x68b27372)
0x68b27362: 45 08 03 c0 eb 28 66 8b 45 08 eb 22 80 7d 18 00
0x68b27372: 66 0f b6 07 75 18 69 c0 01 01 00 00 eb 10 80 7d

Stack: [0x035f0000,0x03630000), sp=0x0362f744, free space=253k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C [GLU32.dll+0x7372]
C [GLU32.dll+0x7bb7]
C [jogl.dll+0x103b1]
j javax.media.opengl.glu.GLU.dispatch_gluBuild2DMipmapsC0(IIIIIILjava/lang/Object;IJ)I+0
j javax.media.opengl.glu.GLU.gluBuild2DMipmapsC(IIIIIILjava/nio/Buffer;)I+56
j javax.media.opengl.glu.GLU.gluBuild2DMipmaps(IIIIIILjava/nio/Buffer;)I+34
j SegyRenderer.initTexture(Ljavax/media/opengl/GL;)V+173
j SegyRenderer.init(Ljavax/media/opengl/GLAutoDrawable;)V+80
j GLDisplay$MyHelpOverlayGLEventListener.init(Ljavax/media/opengl/GLAutoDrawable;)V+29
j com.sun.opengl.impl.GLDrawableHelper.init(Ljavax/media/opengl/GLAutoDrawable;)V+29
j javax.media.opengl.GLCanvas$InitAction.run()V+11
j com.sun.opengl.impl.GLDrawableHelper.invokeGL(Ljavax/media/opengl/GLDrawable;Ljavax/media/opengl/GLContext;Ljava/lang/Runnable;Ljava/lang/Runnable;)V+370
j javax.media.opengl.GLCanvas$DisplayOnEventDispatchThreadAction.run()V+35
j java.awt.event.InvocationEvent.dispatch()V+11
j java.awt.EventQueue.dispatchEvent(Ljava/awt/AWTEvent;)V+26
j java.awt.EventDispatchThread.pumpOneEventForHierarchy(ILjava/awt/Component;)Z+233
j java.awt.EventDispatchThread.pumpEventsForHierarchy(ILjava/awt/Conditional;Ljava/awt/Component;)V+26
j java.awt.EventDispatchThread.pumpEvents(ILjava/awt/Conditional;)V+4
j java.awt.EventDispatchThread.pumpEvents(Ljava/awt/Conditional;)V+3
j java.awt.EventDispatchThread.run()V+9
v ~StubRoutines::call_stub
V [jvm.dll+0x845a9]
V [jvm.dll+0xd9317]
V [jvm.dll+0x8447a]
V [jvm.dll+0x841d7]
V [jvm.dll+0x9ed69]
V [jvm.dll+0x109fe3]
V [jvm.dll+0x109fb1]
C [MSVCRT.dll+0x2a3b0]
C [kernel32.dll+0xb50b]

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j javax.media.opengl.glu.GLU.dispatch_gluBuild2DMipmapsC0(IIIIIILjava/lang/Object;IJ)I+0
j javax.media.opengl.glu.GLU.gluBuild2DMipmapsC(IIIIIILjava/nio/Buffer;)I+56
j javax.media.opengl.glu.GLU.gluBuild2DMipmaps(IIIIIILjava/nio/Buffer;)I+34
j SegyRenderer.initTexture(Ljavax/media/opengl/GL;)V+173
j SegyRenderer.init(Ljavax/media/opengl/GLAutoDrawable;)V+80
j GLDisplay$MyHelpOverlayGLEventListener.init(Ljavax/media/opengl/GLAutoDrawable;)V+29
j com.sun.opengl.impl.GLDrawableHelper.init(Ljavax/media/opengl/GLAutoDrawable;)V+29
j javax.media.opengl.GLCanvas$InitAction.run()V+11
j com.sun.opengl.impl.GLDrawableHelper.invokeGL(Ljavax/media/opengl/GLDrawable;Ljavax/media/opengl/GLContext;Ljava/lang/Runnable;Ljava/lang/Runnable;)V+370
j javax.media.opengl.GLCanvas$DisplayOnEventDispatchThreadAction.run()V+35
j java.awt.event.InvocationEvent.dispatch()V+11
j java.awt.EventQueue.dispatchEvent(Ljava/awt/AWTEvent;)V+26
j java.awt.EventDispatchThread.pumpOneEventForHierarchy(ILjava/awt/Component;)Z+233
j java.awt.EventDispatchThread.pumpEventsForHierarchy(ILjava/awt/Conditional;Ljava/awt/Component;)V+26
j java.awt.EventDispatchThread.pumpEvents(ILjava/awt/Conditional;)V+4
j java.awt.EventDispatchThread.pumpEvents(Ljava/awt/Conditional;)V+3
j java.awt.EventDispatchThread.run()V+9
v ~StubRoutines::call_stub

--------------- P R O C E S S ---------------

Java Threads: ( => current thread )
0x000366c8 JavaThread “DestroyJavaVM” [_thread_blocked, id=2372]
0x02cdfbc8 JavaThread “Timer-0” [_thread_blocked, id=5840]
=>0x02cd7428 JavaThread “AWT-EventQueue-0” [_thread_in_native, id=1724]
0x02ced398 JavaThread “AWT-Windows” daemon [_thread_in_native, id=3888]
0x02ca25f0 JavaThread “AWT-Shutdown” [_thread_blocked, id=5276]
0x00a98e38 JavaThread “Java2D Disposer” daemon [_thread_blocked, id=5948]
0x00a6ee10 JavaThread “Low Memory Detector” daemon [_thread_blocked, id=6032]
0x00a6db10 JavaThread “CompilerThread0” daemon [_thread_blocked, id=3284]
0x00a6cd30 JavaThread “Signal Dispatcher” daemon [_thread_blocked, id=5988]
0x00a47c18 JavaThread “Finalizer” daemon [_thread_blocked, id=3832]
0x0003f780 JavaThread “Reference Handler” daemon [_thread_blocked, id=1408]

--------------- S Y S T E M ---------------

OS: Windows XP Build 2600 Service Pack 2

CPU:total 2 family 15, cmov, cx8, fxsr, mmx, sse, sse2, ht

Memory: 4k page, physical 2095196k(1159760k free), swap 4032708k(2905596k free)

vm_info: Java HotSpot™ Client VM (1.5.0_06-b05) for windows-x86, built on Nov 10 2005 11:12:14 by “java_re” with MS VC++ 6.0

Sorry, but without a test case there is basically nothing we can do. Given that the native GLU implementation is crashing I would recommend checking your code to make sure you have all of the parameters correct to the gluBuild2DMipmaps call.

Would there be any interest in a rewritten gluScaleImage and gluBuild2DMipmaps methods, particularly one that also works for single component textures?

For one of my projects I’ve already written a subset of code that would go towards a java implementation of gluScaleImage, if there’s any interest I could finish it properly in the next month or two as time permits.

That would be very welcome. Based on some code that Romain Guy contributed (ImageUtil.createThumbnail()) it seems that using Java2D for the image scaling operation would be the way to go, and would result in huge code reductions and hopefully bug fixes. The hardest parts would be handling things like packed pixel formats and testing everything. If you’re willing and able to sign the contributor agreement on the JOGL home page and to drive this project I and possibly others would be glad to help it along.

I thought about this many times and the thought of generating all the test cases usually stopped me from attempting it. It would really shrink the amount of code by a ton if you were able to get it to work via java2d calls.

I used to do mipmap generation using Java2D (BufferedImage to be more precise) but I’ve now replaced it with my own scaling routine that works on the raw int/byte/… arrays. The reason I did this was to avoid having to count on RenderingHints to get proper results. After all, these things are hints and not guarantees. If you don’t set the RenderingHints.KEY_INTERPOLATION to RenderingHints.VALUE_INTERPOLATION_BILINEAR it seems that nearest neighbour scaling is used instead, which looks awful. When reducing the size of an image that just doesn’t make any sense at all. My own routine always uses area averaging, which to me seems like the only proper way to generate mipmaps. Since you’re always scaling by a factor of 2, this is also trivial to implement.
Also, and maybe more importantly, the BufferedImage code path was very very slow. The code essentially did the following (maybe someone can point out my mistake that made it so slow)

public static BufferedImage createScaledInstance(BufferedImage image, int aWidth, int aHeight) {
    BufferedImage bufferedimage;
    if (image.getType() == BufferedImage.TYPE_CUSTOM) {
      // Copy the original image properties
      Hashtable imageProperties = new Hashtable();
      String[] imagePropertyNames = image.getPropertyNames();
      if (imagePropertyNames != null) {
        for (int i = 0; i < imagePropertyNames.length; i++) {
          String imagePropertyName = imagePropertyNames[i];
          imageProperties.put(imagePropertyName, image.getProperty(imagePropertyName));
        }
      }

      bufferedimage = new BufferedImage(
              image.getColorModel(),
              image.getRaster().createCompatibleWritableRaster(aWidth, aHeight),
              image.isAlphaPremultiplied(),
              imageProperties
      );
    } else {
      bufferedimage = new BufferedImage(aWidth, aHeight, image.getType());
    }

    Graphics2D g = bufferedimage.createGraphics();
    g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    g.drawImage(image, 0, 0, aWidth, aHeight, null);
    g.dispose();
    return bufferedimage;
}

Because I’m working with tiff files that are decoded by JAI, the image type is TYPE_CUSTOM most of the time. This then forced me to use BufferedImage#getRGB, which is also very slow.
Just my 2 cents…

I think you want to have the target BufferedImage be created via GraphicsConfiguration.createCompatibleImage() in order to get reasonable performance, though I could be wrong about this. I’ll see if the Java2D team can comment.

No, I would not suggest using createCompatibleImage() in this case. That method is useful when you are creating an image that will be rendered to the screen/backbuffer by Java2D. But in this case, we are creating an image that will ultimately be passed down to OpenGL via glTex{Sub}Image() or TextureIO, so it would be better to use a well-known image format for the destination: INT_RGB for opaque images, INT_ARGB for non-opaque. This way you don’t need to deal with arbitrary image formats when it comes time to upload the image to an OpenGL texture; you only need one or two codepaths to handle INT_RGB and INT_ARGB data. Also, we’re more likely to have fast scale/copy loops for those formats than any others.

I agree that Romain’s ImageUtil.createThumbnail() method would be a good starting point. Just change the line that does createCompatibleImage() to use “new BufferedImage(TYPE_INT_[A]RGB)” instead.

Chris

FYI, I finally got around to looking at the test case fcoutel sent me offline and as far as I can tell the problem was due to having the wrong unpack alignment set. The default appears to be 4 which doesn’t work for odd-sized RGB byte-encoded images. glPixelStorei(GL_UNPACK_ALIGNMENT, 1) seems to have solved the problem and I’m waiting for confirmation of this. Basically the Java port of the GLU mipmap code appears to be working correctly and this is something to watch out for if using non-power-of-two images.