WGL_ARB_pixel_format

Why are these functions in GL and not in WGL? You are supposed to use them instead of ChoosePixelFormat which is in WGL.

There are two WGL classes. One contains the extensions in wglext.h and is public in net.java.games.jogl.WGL. The other contains the low-level window system routines from wingdi.h and is private to the implementation in net.java.games.jogl.impl.windows.WGL.java. There are a similar pair of GLX classes.

WGL_ARB_pixel_format is in the public WGL interface, not in the public GL interface. It’s used internally to implement JOGL’s pbuffer support.

WGL_ARB_pixel_format is a replacement for ChoosePixelFormat so I thought it a little odd that one was exposed via WGL and the other via GL. I am looking into getting Pbuffers working. I am assuming that they don’t work at all in their current form since the little test app I whipped up kept throwing PROC_NOT_FOUND errors in WindowsPbufferGLContext with the binaries from the cvs. I have tinkered a little with them, even added CreateDC to WGL, and so far I can generate PROC_NOT_FOUND, INVALID_PIXEL_FORMAT, INVALID_HANDLE, and what I think is a driver error of -1. I am pretty proud of the -1. My understanding of windows gdi is pretty thin so any ideas are appreciated.

JOGL’s pbuffer support has been tested and is known to work on both Windows and X11, at least with NVidia hardware (tested with GeForce 3 and above). I haven’t done a lot of testing with ATI hardware yet. Do you have a recent card that has pbuffer support, and are you using your vendor’s latest drivers?

Part of my problem was solved by setting double buffering to false. I finally saw the comment in the swapbuffers function. Supposedly you need to expose wglSwapLayerBuffer to allow double buffering of pbuffers. I can now render to texture but not with texture rectangle. I have a gf2 with the lastest drivers which support the nv extension. Also I notice you used glCopyTexImage2D instead of glCopyTexSubImages2D. I don’t know if it is still true but the latter used to be recommended because it was faster.

WindowsPbufferGLContext.java


protected void swapBuffers() throws GLException {
    // FIXME: do we need to do anything if the pbuffer is double-buffered?
    // For now, just grab the pixels for the render-to-texture support.
    if (rtt && !rect) {
      if (DEBUG) {
        System.err.println("Copying pbuffer data to GL_TEXTURE_2D state");
      }

      GL gl = getGL();
      gl.glCopyTexSubImage2D(textureTarget, 0, GL.GL_RGB, 0, 0, width, height, 0);
    }
  }

Change glCopyTexImage2D to glCopyTexSubImage2D.


public void bindPbufferToTexture() {
    if (!rtt) {
      throw new GLException("Shouldn't try to bind a pbuffer to a texture if render-to-texture hasn't been " +
                            "specified in its GLCapabilities");
    }
    GL gl = getGL();
    gl.glBindTexture(textureTarget, texture);
    // Note: this test was on the rtt variable in NVidia's code but I
    // think it doesn't make sense written that way
    if (rtt) {
      if (!gl.wglBindTexImageARB(buffer, GL.WGL_FRONT_LEFT_ARB)) {
        throw new GLException("Binding of pbuffer to texture failed: " + wglGetLastError());
      }
    }
    // Note that if the render-to-texture-rectangle extension is not
    // specified, we perform a glCopyTexImage2D in swapBuffers().
  }

  public void releasePbufferFromTexture() {
    if (!rtt) {
      throw new GLException("Shouldn't try to bind a pbuffer to a texture if render-to-texture hasn't been " +
                            "specified in its GLCapabilities");
    }
    if (rtt) {
      GL gl = getGL();
      if (!gl.wglReleaseTexImageARB(buffer, GL.WGL_FRONT_LEFT_ARB)) {
        throw new GLException("Releasing of pbuffer from texture failed: " + wglGetLastError());
      }
    }
  }

The note says checking against rtt makes no sense but that is the correct check. These two three changes resulted in an order of magnatude speed increase in my test app.

WindowsPbufferGLContext.java


protected synchronized boolean makeCurrent(Runnable initAction) throws GLException {
    created = false;

    if (buffer == 0) {
      // pbuffer not instantiated yet
      return false;
    }

    boolean res = super.makeCurrent(initAction);
    if (created) {
      // Initialize render-to-texture support if requested
      rtt  = capabilities.getOffscreenRenderToTexture();
      rect = capabilities.getOffscreenRenderToTextureRectangle();
      
      if (rtt) {
        if (DEBUG) {
          System.err.println("Initializing render-to-texture support");
        }

        GL gl = getGL();
        if (rect && !gl.isExtensionAvailable("GL_NV_texture_rectangle")) {
          System.err.println("WindowsPbufferGLContext: WARNING: GL_NV_texture_rectangle extension not " +
                             "supported; skipping requested render_to_texture_rectangle support for pbuffer");
          rect = false;
        }
        if (rect) {
          if (DEBUG) {
            System.err.println("  Using render-to-texture-rectangle");
          }
          textureTarget = GL.GL_TEXTURE_RECTANGLE_NV;
        } else {
          if (DEBUG) {
            System.err.println("  Using vanilla render-to-texture");
          }
          textureTarget = GL.GL_TEXTURE_2D;
        }
      if( texture == 0 ) {
        int[] tmp = new int[1];
      gl.glGenTextures(1, tmp);
      texture = tmp[0];
      gl.glBindTexture(textureTarget, texture);
      gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST);
      gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
      gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE);
      gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE);
      gl.glCopyTexSubImage2D(textureTarget, 0, GL.GL_RGBA, 0, 0, width, height, 0);
      }
      }
    }
    return res;
  }

Just put a check around the texture creation code. You shouldn’t have to generate a new ID every frame.

Also you can remove all the code from swapbuffers since if you make the other changes you don’t need it. If doublebuffered pbuffers are introduced the wglSwapLayerBuffers call should go there.

Thanks for your suggestions. I’ve applied your suggestion to use glCopyTexSubImage2D instead of glCopyTexImage2D. Thanks also for pointing out that the rect test should have been rtt; that code was ported in a hurry and I didn’t sit down and look at how the extensions worked. The code that’s currently in the source tree should work better. I’ll produce a new binary release soon.

Note that almost all of the code in makeCurrent is guarded by a test of the “created” flag, which is only set when the pbuffer is actually instantiated. A new texture ID isn’t generated every frame. Because of the semantics of when the internal swapBuffers is called, in the case of using multiple pbuffers in a chain, any copy-back needs to occur there instead of makeCurrent.

A couple other to do’s I will look into -

A destroy method. Right now it looks like WindowsOffscreenGLContext is the only class with a destroy method. Perhaps there should be a method in GLContext. I don’t know how drivers handle not having a rendering context not destroyed but it would probably be good to do it manually. I notice in the current code you are not creating a new context with the pbuffer dc you create with wglGetPbufferDCARB. Any reason for that?


public void destroy() {
    wglDeleteContext( hglrc? );
    wglReleasePbufferDCARB( buffer, hdc );
    wglDestroyPbufferARB( buffer );
}

I got a little tunnel vision with the render to texture part and I see that we still need swapbuffer code for non-render to texture pbuffers. Though you have to have a pretty old card to not have hardware rtt.

Right now you can’t generate mipmaps if you use render to texture. You can’t use them for texture rectangles but normal rtt is cool.

The current code only supports rgb textures. Obviously an alpha channel would be nice.

Never mind about the call to wglCreateContext I see it is there in create(). THe new code looks good and rtt and rttr both work just fine now though it is about %15 slower than the c demo I am apeing, but that is a lot better than the %90 slower that it started out at.

Only need to change a couple lines to get GLJPanel up and running on windows—

WindowsOffscreenGLContext
From


// CreateDIBSection doesn't really need the device context if we are
    // producing a truecolor bitmap.
    hbitmap = WGL.CreateDIBSection(0, info, WGL.DIB_RGB_COLORS, 0, 0, 0);
    if (hbitmap == 0) {
      throw new GLException("Error creating offscreen bitmap");
    }
    hdc = WGL.CreateCompatibleDC(0);
    if (hdc == 0) {
      throw new GLException("Error creating device context for offscreen OpenGL context");
    }
    if ((origbitmap = WGL.SelectObject(hdc, hbitmap)) == 0) {
      throw new GLException("Error selecting bitmap into new device context");
    }
    
    choosePixelFormatAndCreateContext(false);

To


hdc = WGL.CreateCompatibleDC(0);
if (hdc == 0) {
    System.out.println("LastError: " + WGL.GetLastError() );
    throw new GLException("Error creating device context for offscreen OpenGL context");
}
hbitmap = WGL.CreateDIBSection(hdc, info, WGL.DIB_RGB_COLORS, 0, 0, 0);
    if (hbitmap == 0) {
      throw new GLException("Error creating offscreen bitmap");
    }
    if ((origbitmap = WGL.SelectObject(hdc, hbitmap)) == 0) {
      throw new GLException("Error selecting bitmap into new device context");
    }
    
    choosePixelFormatAndCreateContext(false);

Then there is the larger issue of DefaultGLCapabilitiesChooser. It will never, or probably never, issue a pixelformat that support offscreen renderiing in its current state. Putting

WindowsGLContext


// Helper routine for the overridden create() to call
  protected void choosePixelFormatAndCreateContext(boolean onscreen) {
    PIXELFORMATDESCRIPTOR pfd = null;
    int pixelFormat = 0;
    if (chooser == null) {
      // Note: this code path isn't taken any more now that the
      // DefaultGLCapabilitiesChooser is present. However, it is being
      // left in place for debugging purposes.
      pfd = glCapabilities2PFD(capabilities, onscreen);
      pixelFormat = WGL.ChoosePixelFormat(hdc, pfd);
      if (pixelFormat == 0) {
        throw new GLException("Unable to choose appropriate pixel format");
      }
      if (DEBUG) {
        System.err.println("Chosen pixel format from ChoosePixelFormat:");
      PIXELFORMATDESCRIPTOR tmpPFD = new PIXELFORMATDESCRIPTOR();
      WGL.DescribePixelFormat(hdc, pixelFormat, tmpPFD.size(), tmpPFD);
        System.err.println(pfd2GLCapabilities(tmpPFD));
      }
    } else {
        // NEW CODE********************
        if( onscreen ) {
      int numFormats = WGL.DescribePixelFormat(hdc, 1, 0, null);
      if (numFormats == 0) {
        throw new GLException("Unable to enumerate pixel formats of window for GLCapabilitiesChooser");
      }
      GLCapabilities[] availableCaps = new GLCapabilities[numFormats];
      pfd = new PIXELFORMATDESCRIPTOR();
      for (int i = 0; i < numFormats; i++) {
        if (WGL.DescribePixelFormat(hdc, 1 + i, pfd.size(), pfd) == 0) {
          throw new GLException("Error describing pixel format " + (1 + i) + " of device context");
        }
        availableCaps[i] = pfd2GLCapabilities(pfd);
      }
      // Supply information to chooser
      pixelFormat = chooser.chooseCapabilities(capabilities, availableCaps);
      if ((pixelFormat < 0) || (pixelFormat >= numFormats)) {
        throw new GLException("Invalid result " + pixelFormat +
                              " from GLCapabilitiesChooser (should be between 0 and " +
                              (numFormats - 1) + ")");
      }
      if (DEBUG) {
        System.err.println("Chosen pixel format (" + pixelFormat + "):");
        System.err.println(availableCaps[pixelFormat]);
      }
      pixelFormat += 1; // one-base the index
      if (WGL.DescribePixelFormat(hdc, pixelFormat, pfd.size(), pfd) == 0) {
        throw new GLException("Error re-describing the chosen pixel format");
      }
        // NEW CODE********************
        } else {
            pfd = glCapabilities2PFD(capabilities, onscreen);
            pixelFormat = WGL.ChoosePixelFormat(hdc, pfd);
        }
    }
    if (!WGL.SetPixelFormat(hdc, pixelFormat, pfd)) {
      throw new GLException("Unable to set pixel format");
    }
    hglrc = WGL.wglCreateContext(hdc);
    if (hglrc == 0) {
      throw new GLException("Unable to create OpenGL context");
    }
  }

in choosePixelFormatAndCreateContext of WindowsGLContext will sovle the problem but it isn’t really optimal. This brings me back to not having wglChoosePixelFormat exposed in WGL. The windows gdi call ChoosePixelFormat will not report certain valid pixel formats which are supported by the icd. It also may be reporting invalid pixel formats in the case of offscreen rendering. The default choosers top choice for rendering to a bitmap is a onscreen window. Obviously something is wrong with that.

Thanks for your fixes. Originally GLJPanel was working with the DefaultGLCapabilitiesChooser but changes in the selection algorithm (probably either for Solaris or Linux) caused the Windows offscreen rendering support to get broken. To avoid breaking the other platforms I’ve left the default selection algorithm alone for now and applied your fixes although this means that users won’t be able to manually choose a pixel format for GLJPanels on Windows. Hopefully I or someone else can look into this again in the near future.

It is unfortunate that DIBs are only supported by ms’s software renderer. I got a pbuffer version of the offscreen context working but I had to create a invisible window with some native code. I just could not get it to work any other way. It is twice as fast as dib’s and you get all your icd functions though.

I saw your post on another thread on this topic and would like to say that that’s pretty cool, I appreciate your tenacity. Do you think it might be possible to get this functionality to work by using the AWT’s new routines to make a borderless top-level window containing a zero-by-zero-sized GLCanvas in order to get the root window for creating the subordinate pbuffer? If we could do that then it would be portable and that would be great. It would also be necessary to think about having the existing DIB/Pixmap (on Windows/X11) GLJPanel implementation be available as a fallback in case pbuffers aren’t supported.

Failing through to a dib context was my plan also. A awt Window might work, I will give it a try on monday. Another thing I forgot to mention is that if you use an animator with a gljpanel and you stop the animator before closing the frame it hangs the main event thread which stops any further event processing. Checking against EventQueue.isDispatchThread and returning if true, before you call wait on the thread seems to fix the problem.

Could you please file a bug about this on the JOGL Issues page? Thanks.

The invisible window seems to work. I have to do a little more testing and do a little clean up and I will post the code from home.

I filed the bug, #37, it probably isn’t worded correctly but basically I have been testing the windows version of GLJPanels with NeHe’s Lesson 6 and when the app recieves a windowclosing event it calls animator.stop as part of the clean up. I am pretty sure the stop method is putting the event dispatch thread to sleep so it never actually gets to the next line after animator.stop which is System.exit.

I lied, it doesn’t work so well after the initial startup. When I resize the jframe it generates an error in WindowsOnscreenGLContext


if ((res & JAWTFactory.JAWT_LOCK_ERROR) != 0) {
      throw new GLException("Unable to lock surface");
    }

If you could explain what is going on here I would appreciate it.

Just had to remove one line and it works just fine now. Every once in a while it says it can’t destroy the old pbuffer but other than that it is good. I tried iWindow.dispose to clean up after myself but that always seems to hang the app. No idea how to fix that or if it even matters.
WindowsOffscreenGLContext


protected void create() {
    if( usePbuffer ) {
                  Component c = component;
                  while( !(c instanceof Window) ) {
                        c = c.getParent();
                  }
                  Window iWindow = new Window((Window)c);
                  iWindow.setSize( 0, 0 );
                  iWindow.setLocation( 0, 0 );
                  GLCanvas iGLCanvas = GLDrawableFactory.getFactory().createGLCanvas(new GLCapabilities() );
                  iWindow.add( iGLCanvas );
                  iWindow.setVisible( true );
                  WindowsGLContext temp = wpbContext;
                  wpbContext = (WindowsGLContext)((GLPbufferImpl)iGLCanvas.createOffscreenDrawable( capabilities, width, height )).getContext();
                  iGLCanvas.display();
                  wpbContext.create();
                  hdc = wpbContext.hdc;
                  hglrc = wpbContext.hglrc;
                  if( temp != null ) {
                        ((WindowsPbufferGLContext)temp).destroy();
                  }
                  iWindow.setVisible( false );
                  if( (hdc == 0) || (hglrc == 0) ) {
                        // Failed to create pbuffer, use dib
                        usePbuffer = false;
                  }
                  
    }
            if( !usePbuffer ) {
                  BITMAPINFO info = new BITMAPINFO();
                  BITMAPINFOHEADER header = info.bmiHeader();
                  int bitsPerPixel = (capabilities.getRedBits() +
                        capabilities.getGreenBits() +
                        capabilities.getBlueBits());
                  header.biSize(header.size());
                  header.biWidth(width);
                  // NOTE: negating the height causes the DIB to be in top-down row
                  // order rather than bottom-up; ends up being correct during pixel
                  // readback
                  header.biHeight(-1 * height);
                  header.biPlanes((short) 1);
                  header.biBitCount((short) bitsPerPixel);
                  header.biXPelsPerMeter(0);
                  header.biYPelsPerMeter(0);
                  header.biClrUsed(0);
                  header.biClrImportant(0);
                  header.biCompression(WGL.BI_RGB);
                  header.biSizeImage(width * height * bitsPerPixel / 8);

                  // CreateDIBSection doesn't really need the device context if we are
                  // producing a truecolor bitmap.
                  hbitmap = WGL.CreateDIBSection(0, info, WGL.DIB_RGB_COLORS, 0, 0, 0);
                  if (hbitmap == 0) {
                        throw new GLException("Error creating offscreen bitmap");
                  }
                  hdc = WGL.CreateCompatibleDC(0);
                  if (hdc == 0) {
                        throw new GLException("Error creating device context for offscreen OpenGL context");
                  }
                  if ((origbitmap = WGL.SelectObject(hdc, hbitmap)) == 0) {
                        throw new GLException("Error selecting bitmap into new device context");
                  }
                  
                  choosePixelFormatAndCreateContext(false);
            }
      }


private void destroy() {
            if( usePbuffer ) {
                  // Destroying the old pbuffer happens after creation of new pbuffer
                  hdc = 0;
                  hglrc = 0;
            } else {
    // Must destroy OpenGL context, bitmap and device context
    WGL.wglDeleteContext(hglrc);
    WGL.SelectObject(hdc, origbitmap);
    WGL.DeleteObject(hbitmap);
    WGL.DeleteDC(hdc);
    hglrc = 0;
    origbitmap = 0;
    hbitmap = 0;
    hdc = 0;
    GLContextShareSet.contextDestroyed(this);
  }
      }

WindowsPbufferGLContext


protected void destroy() {
            if( !WGL.wglDeleteContext( hglrc ) ) {
                  System.out.println("Error Destroying HGLRC");
            }
            hglrc = 0;
            if( (hdc > 0) && (gl.wglReleasePbufferDCARB( buffer, hdc ) == 0) ) {
                  System.out.println("Error Releasing Pbuffer");
            }
            if( !gl.wglDestroyPbufferARB( buffer ) ) {
                  System.out.println("Error Destroying Pbuffer: " + WGL.GetLastError() );
            }
            hdc = 0;
            buffer = 0;
      }

I realized that I didn’t post all of the changes I made to get pbuffers to work with an offscreen context.

WindowsGLContextFactory


public class WindowsGLContextFactory extends GLContextFactory {
  public GLContext createGLContext(Component component,
                                   GLCapabilities capabilities,
                                   GLCapabilitiesChooser chooser,
                                   GLContext shareWith) {
    if (component instanceof Canvas) {
      return new WindowsOnscreenGLContext(component, capabilities, chooser, shareWith);
    } else {
      return new WindowsOffscreenGLContext(component, capabilities, chooser, shareWith);
    }
  }
}

Offscreen will need to have access to it parents to add the invisible Window later so instead of setting component to null I set it to component. WindowsOffscreenGLContext will also have to be changed.

WindowsOffscreenGLContext


public WindowsOffscreenGLContext(Component comp,
                                                                                                       GLCapabilities capabilities,
                                   GLCapabilitiesChooser chooser,
                                   GLContext shareWith) {
    super(comp, capabilities, chooser, shareWith);
  }

Also Issue 41 can be fixed by setting double buffering to be false right before the DIB is created. That will guarentee that we don’t try to use an invalid pixelformat.

WindowsOffscreenGLContext


if( !usePbuffer ) {
                  
                  BITMAPINFO info = new BITMAPINFO();
                  BITMAPINFOHEADER header = info.bmiHeader();
                  int bitsPerPixel = (capabilities.getRedBits() +
                        capabilities.getGreenBits() +
                        capabilities.getBlueBits());
                  header.biSize(header.size());
                  header.biWidth(width);
                  // NOTE: negating the height causes the DIB to be in top-down row
                  // order rather than bottom-up; ends up being correct during pixel
                  // readback
                  header.biHeight(-1 * height);
                  header.biPlanes((short) 1);
                  header.biBitCount((short) bitsPerPixel);
                  header.biXPelsPerMeter(0);
                  header.biYPelsPerMeter(0);
                  header.biClrUsed(0);
                  header.biClrImportant(0);
                  header.biCompression(WGL.BI_RGB);
                  header.biSizeImage(width * height * bitsPerPixel / 8);

                  // CreateDIBSection doesn't really need the device context if we are
                  // producing a truecolor bitmap.
                  hbitmap = WGL.CreateDIBSection(0, info, WGL.DIB_RGB_COLORS, 0, 0, 0);
                  if (hbitmap == 0) {
                        throw new GLException("Error creating offscreen bitmap");
                  }
                  hdc = WGL.CreateCompatibleDC(0);
                  if (hdc == 0) {
                        throw new GLException("Error creating device context for offscreen OpenGL context");
                  }
                  if ((origbitmap = WGL.SelectObject(hdc, hbitmap)) == 0) {
                        throw new GLException("Error selecting bitmap into new device context");
                  }
                  // Make sure we ask for a single buffered pixel format
                  capabilities.setDoubleBuffered( false );
                  choosePixelFormatAndCreateContext(false);
            }

This also made me realize that I never tested against doublebuffered pbuffers which can leagally be requested. There will need to be a some code for a bufferswap. According to microsoft calling SwapBuffers(hdc) on a singlebuffered context will result in a NOP so this seems to work just fine for me.

WindowsGLOffscreenContext


protected synchronized void swapBuffers() throws GLException {
            if( !WGL.SwapBuffers( hdc ) ) {
                  System.out.println("Error Swapping Buffers");
            }
  }