Incorrect rendering to pbuffer

I’m using JOGL for writing an offline, non-realtime renderer. Rather than rendering to any on-screen component, I simply create a pbuffer and render to that.

This partly works, but for some reason the render comes out distorted: stretched by different amounts in each dimension, and offset from where it ought to be. A couple of images will show what I mean. Here is a correct render, showing what the image ought to look like:

and here is what I actually get:

To be clear: these two images were generated by exactly the same code. The only difference is that one was rendered to a pbuffer created as follows:

GLPbuffer pbuffer = GLDrawableFactory.getFactory().createGLPbuffer(new GLCapabilities(), new DefaultGLCapabilitiesChooser(), width, height, null);

while the other was rendered to a GLCanvas created with:

GLCanvas canvas = new GLCanvas();

Does anyone have any idea what’s going on? This is with JOGL JSR-231 1.1.0 and Java 1.5 running on Mac OS X 10.5.1.

Peter

Best guess is your projection and modelview matrix setup code is making some incorrect assumptions.

I’d recommend boiling things down into as small a test case as possible and posting it or posting a link. Simplifying things may also help uncover the problem.

I’ll see if I can boil it down to a simple test case. To be clear, though, the matrix setup code is identical between the two images. The difference between them is truly nothing but a one line change in whether I render to the GLCanvas or the GLPBuffer. What sort of incorrect assumptions did you have in mind?

Peter

Not quite sure. Perhaps the pbuffer’s dimensions were silently rounded up to a power of two by the OpenGL implementation and you somehow need to account for that in your code. Need a test case to make further suggestions.

Ok, here’s a self contained test case. It defines a GLEventListener that draws a yellow rectangle centered on a magenta background, then saves it to an Image. The main() method creates a GLPbuffer, adds the listener, then displays the saved image in a window. The rectangle comes out in the wrong place. But if you replace the pbuffer with a GLCanvas, it looks correct.

Everything after the GLEventListener definition is the code for grabbing the image, which I copied from http://www.felixgers.de/teaching/jogl/imagingProg.html. I’m pretty sure the problem has nothing to do with this code: if I use it on a GLCanvas, it produces an accurate copy of what’s shown on the screen.

Peter

import javax.media.opengl.*;
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.nio.*;

public class BugDemo
{
  public static void main(String args[])
  {
    JFrame fr = new JFrame();
    GLPbuffer pbuffer = GLDrawableFactory.getFactory().createGLPbuffer(new GLCapabilities(), new DefaultGLCapabilitiesChooser(), 400, 300, null);
    GLListener listener = new GLListener();
    pbuffer.addGLEventListener(listener);
    pbuffer.display();
    pbuffer.destroy();
    fr.add(new JLabel(new ImageIcon(listener.img)), BorderLayout.CENTER);

    // To see a correct rendering, remove the six lines immediately above and replace them
    // with the following four lines:

    // GLCanvas canvas = new GLCanvas();
    // canvas.addGLEventListener(new GLListener());
    // canvas.setPreferredSize(new Dimension(400, 300));
    // fr.add(canvas, BorderLayout.CENTER);

    fr.pack();
    fr.setVisible(true);
  }

  public static class GLListener implements GLEventListener
  {
    public Image img;

    public void init(GLAutoDrawable drawable)
    {
      GL gl = drawable.getGL();
      gl.glDisable(GL.GL_LIGHTING);
    }

    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height)
    {
    }

    public void display(GLAutoDrawable drawable)
    {
      GL gl = drawable.getGL();
      gl.glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
      gl.glClear(GL.GL_COLOR_BUFFER_BIT+GL.GL_DEPTH_BUFFER_BIT);
      gl.glMatrixMode(GL.GL_PROJECTION);
      gl.glLoadIdentity();
      gl.glFrustum(-10.0, 10.0, -10.0, 10.0, 1.0, 100.0);
      gl.glMatrixMode(GL.GL_MODELVIEW);
      gl.glLoadIdentity();
      gl.glLoadMatrixd(new double [] {
          -1, 0, 0, 0,
          0, 1, 0, 0,
          0, 0, -1, 0,
          0, 0, 0, 1
      }, 0);
      gl.glColor3f(255.0f, 255.0f, 0.0f);
      gl.glBegin(GL.GL_QUADS);
      gl.glVertex3f(-20.0f, 20.0f, 10.0f);
      gl.glVertex3f(-20.0f, -20.0f, 10.0f);
      gl.glVertex3f(20.0f, -20.0f, 10.0f);
      gl.glVertex3f(20.0f, 20.0f, 10.0f);
      gl.glEnd();
      img = copyFrame(gl, 400, 300);
    }

    public void displayChanged(GLAutoDrawable drawable, boolean arg1, boolean arg2)
    {
    }
  }

  private static ByteBuffer getFrameData( GL gl, ByteBuffer pixelsRGB, int width, int height )
    {
      // Read Frame back into our ByteBuffer.
      gl.glReadBuffer( GL.GL_BACK );
      gl.glPixelStorei( GL.GL_PACK_ALIGNMENT, 1 );
      gl.glReadPixels( 0, 0, width, height,
		       GL.GL_RGB, GL.GL_UNSIGNED_BYTE,
		       pixelsRGB );

      return pixelsRGB;
    }


  private static BufferedImage copyFrame( GL gl, int width, int height )
    {
      // Create a ByteBuffer to hold the frame data.
      java.nio.ByteBuffer pixelsRGB = ByteBuffer.allocateDirect( width * height * 3 );

      // Get date from frame as ByteBuffer.
      getFrameData( gl, pixelsRGB, width, height );

      return transformPixelsRGBBuffer2ARGB_ByHand( pixelsRGB, width, height );
    }


  // Copies the Frame to an integer array.
  // Do the necessary conversion by hand.
  //
  private static BufferedImage transformPixelsRGBBuffer2ARGB_ByHand( ByteBuffer pixelsRGB, int width, int height )
    {
      // Transform the ByteBuffer and get it as pixeldata.

      int[] pixelInts = new int[ width * height ];

      // Convert RGB bytes to ARGB ints with no transparency.
      // Flip image vertically by reading the
      // rows of pixels in the byte buffer in reverse
      // - (0,0) is at bottom left in OpenGL.
      //
      // Points to first byte (red) in each row.
      int p = width * height * 3;
      int q; // Index into ByteBuffer
      int i = 0; // Index into target int[]
      int w3 = width * 3; // Number of bytes in each row
      for (int row = 0; row < height; row++) {
	p -= w3;
	q = p;
	for (int col = 0; col < width; col++) {
	  int iR = pixelsRGB.get(q++);
	  int iG = pixelsRGB.get(q++);
	  int iB = pixelsRGB.get(q++);
	  pixelInts[i++] =
	    0xFF000000 | ((iR & 0x000000FF) << 16) |
	    ((iG & 0x000000FF) << 8) | (iB & 0x000000FF);
	}
      }

      // Create a new BufferedImage from the pixeldata.
      BufferedImage bufferedImage =
	new BufferedImage( width, height,
			   BufferedImage.TYPE_INT_ARGB);
      bufferedImage.setRGB( 0, 0, width, height,
			    pixelInts, 0, width );

      return bufferedImage;
    }
}

Both methods produce the same result on an old ATI Mobility X600.

For further testing you might want to take a look at using glGetDoublev() with GL_MODELVIEW_MATRIX and/or GL_PROJECTION_MATRIX to further investigate what is going on.

Both produce identical results on an old NVidia Quadro FX Go700.

What graphics card is in your machine?

This might be something you would want to report to Apple.

Have you tried using a frame buffer object (FBO) instead of a pbuffer for your offscreen rendering? FBOs may be better supported than pbuffers at this point.

I have a GeForce FX 5200. What OS/Java version/JOGL version did you test it on?

How do I use an FBO instead of a pbuffer? I was under the impression that JOGL didn’t support them yet, but perhaps I was wrong.

Peter

WindowsXP SP2, Java 1.6.0.0_03, and JOGL 1.1.0

[quote]How do I use an FBO instead of a pbuffer? I was under the impression that JOGL didn’t support them yet, but perhaps I was wrong.

Peter
[/quote]
Yup, FBOs are just an extension, so JOGL certainly supports them. Though it’s another story whether or not your GPU supports them…
http://oss.sgi.com/projects/ogl-sample/registry/EXT/framebuffer_object.txt

Hi Peter,

I can confirm I see a similar problem from here. The yellow box is towards the top right with pbuffers rather than centered.

I’m also osx leopard based…

GL_RENDERER=NVIDIA GeForce 8600M GT OpenGL Engine
DRAWABLE_GL=com.sun.opengl.impl.GLImpl
JAVA=1.5.0_13

Cheers

Peter

I must be missing something. To create an FBO, I need a GL object. To get a GL, I need a GLDrawable. And the only GLDrawables I know of are GLCanvas, GLJPanel, and GLPbuffer.

Or are you suggesting that I create a GLPbuffer and call display() on it, but then have my GLEventListener immediately create an FBO, do all its drawing into that, and completely ignore the original pbuffer?

Peter

Since you’re having problems with the pbuffer I’d instead recommend that you create a GLCanvas, create and bind you’re FBO in init(), and then draw in display() like you otherwise would. This should draw to the offscreen FBO.

Then if for what ever reason you want to draw to the GLCanvas as per usual just call, gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, 0);

then to get back to your FBO just call,
gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, myFBO);

This can be done as often as you’d like.

Yes, that’s the idea. Or use Chris’s idea above to create for example a 1x1 GLCanvas in an undecorated Frame and attach the FBO to that.

Just another confirmation of his test case, it doesn’t give correct results on my Macbook pro, os X 10.4.11, geforce 8600 mt

Sounds like it’s definitely a Mac thing, then. The question is whether it’s a bug in Apple’s pbuffer implementation, or in the JOGL native libraries for Macs. I’ll leave that question to those who know a lot more about it than I do.

Peter

Success! I converted it to use a FBO like you suggested, and now it works! So while there’s definitely a bug in there, I have a workaround which is good enough for my present purposes. :smiley:

Thank you to everyone who helped!

Peter

I’ve looked into this more deeply and JOGL’s Mac OS X pbuffer code is rounding up the width and height of the pbuffer to the next power of two, which is required per the documentation of NSOpenGLPixelBuffer when using the GL_TEXTURE_2D target, which it prefers. You can see this in the returned GLPbuffer object, which is 512x512. While JOGL could be doing better here on more recent hardware, I think you have to assume that this might happen and deal with it in your application’s code. If you’d like me to add support for ARB_texture_non_power_of_two pbuffers on Mac OS X, please file an RFE with the JOGL Issue Tracker.

[quote=“Ken Russell,post:17,topic:31398”]
Done! Here’s the RFE:

https://jogl.dev.java.net/issues/show_bug.cgi?id=345

I also included two other suggestions: that if it can’t create a pbuffer of the size you requested, it should throw an exception rather than giving you one of a different size; and that the javadocs should discuss the possible restrictions on size.

Peter