diff options
Diffstat (limited to 'src/net/java/games/jogl/GLJPanel.java')
-rw-r--r-- | src/net/java/games/jogl/GLJPanel.java | 547 |
1 files changed, 415 insertions, 132 deletions
diff --git a/src/net/java/games/jogl/GLJPanel.java b/src/net/java/games/jogl/GLJPanel.java index 3ac5c984c..425160494 100644 --- a/src/net/java/games/jogl/GLJPanel.java +++ b/src/net/java/games/jogl/GLJPanel.java @@ -40,12 +40,15 @@ package net.java.games.jogl; import java.awt.Component; +import java.awt.EventQueue; +import java.awt.Frame; import java.awt.Graphics; import java.awt.GraphicsConfiguration; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferInt; +import java.security.*; import javax.swing.JComponent; import javax.swing.JPanel; import net.java.games.jogl.impl.*; @@ -57,28 +60,47 @@ import net.java.games.jogl.impl.*; /** A lightweight Swing component which provides OpenGL rendering support. Provided for compatibility with Swing user interfaces when adding a heavyweight doesn't work either because of - Z-ordering or LayoutManager problems. Currently implemented using - offscreen (i.e., non-hardware accelerated) rendering, so - performance will likely be poor. This class can not be + Z-ordering or LayoutManager problems. This component attempts to + use hardware-accelerated rendering via pbuffers and falls back on + to software rendering if problems occur. This class can not be instantiated directly; use {@link GLDrawableFactory} to construct them. */ public final class GLJPanel extends JPanel implements GLDrawable { private GLDrawableHelper drawableHelper = new GLDrawableHelper(); - private GLContext context; - private BufferedImage offscreenImage; - private int awtFormat; - private int glFormat; - private int glType; - private int glComps; + + // Data used for either pbuffers or pixmap-based offscreen surfaces + private GLCapabilities offscreenCaps; + private GLCapabilitiesChooser chooser; + private GLDrawable shareWith; + private BufferedImage offscreenImage; + private int neededOffscreenImageWidth; + private int neededOffscreenImageHeight; private DataBufferByte dbByte; private DataBufferInt dbInt; private Object semaphore = new Object(); - private boolean repaintDone; + private int panelWidth = 0; + private int panelHeight = 0; + private Updater updater; + private int awtFormat; + private int glFormat; + private int glType; + + // Implementation using pbuffers + private static boolean hardwareAccelerationDisabled = + Debug.isPropertyDefined("jogl.gljpanel.nohw"); + private boolean pbufferInitializationCompleted; + private GLPbuffer pbuffer; + private int pbufferWidth = 256; + private int pbufferHeight = 256; + private GLCanvas heavyweight; + private Frame toplevel; + + // Implementation using software rendering + private GLContext offscreenContext; // For saving/restoring of OpenGL state during ReadPixels private int[] swapbytes = new int[1]; - private int[] lsbfirst = new int[1]; private int[] rowlength = new int[1]; private int[] skiprows = new int[1]; private int[] skippixels = new int[1]; @@ -86,31 +108,76 @@ public final class GLJPanel extends JPanel implements GLDrawable { GLJPanel(GLCapabilities capabilities, GLCapabilitiesChooser chooser, GLDrawable shareWith) { super(); - context = GLContextFactory.getFactory().createGLContext(null, capabilities, chooser, - GLContextHelper.getContext(shareWith)); + + // Works around problems on many vendors' cards; we don't need a + // back buffer for the offscreen surface anyway + offscreenCaps = (GLCapabilities) capabilities.clone(); + offscreenCaps.setDoubleBuffered(false); + this.chooser = chooser; + this.shareWith = shareWith; + + initialize(); } public void display() { - // Multithreaded redrawing of Swing components is not allowed - try { - synchronized(semaphore) { - repaintDone = false; - repaint(); - while (!repaintDone) { - semaphore.wait(); + if (EventQueue.isDispatchThread()) { + // Want display() to be synchronous, so call paintImmediately() + paintImmediately(0, 0, getWidth(), getHeight()); + } else { + // Multithreaded redrawing of Swing components is not allowed, + // so do everything on the event dispatch thread + try { + // Wait a reasonable period of time for the repaint to + // complete, so that we don't swamp the AWT event queue thread + // with repaint requests. We used to have an explicit flag to + // detect when the repaint completed; unfortunately, under + // some circumstances, the top-level window can be torn down + // while we're waiting for the repaint to complete, which will + // never happen. It doesn't look like there's enough + // information in the EventQueue to figure out whether there + // are pending events without posting to the queue, which we + // don't want to do during shutdown, and adding a + // HierarchyListener and watching for displayability events + // might be fragile since we don't know exactly how this + // component will be used in users' applications. For these + // reasons we simply wait up to a brief period of time for the + // repaint to complete. + synchronized(semaphore) { + repaint(); + semaphore.wait(100); } + } catch (InterruptedException e) { } - } catch (InterruptedException e) { } } - /** Overridden from JComponent; calls {@link #display}. Should not - be invoked by applications directly. */ + /** Overridden from JComponent; calls {@link + GLEventListener#display}. Should not be invoked by applications + directly. */ public void paintComponent(Graphics g) { - displayAction.setGraphics(g); - context.invokeGL(displayAction, false, initAction); + updater.setGraphics(g); + if (!hardwareAccelerationDisabled) { + if (!pbufferInitializationCompleted) { + try { + heavyweight.display(); + pbuffer.display(); + } catch (GLException e) { + // We consider any exception thrown during updating of the + // heavyweight or pbuffer during the initialization phases + // to be an indication that there was a problem + // instantiating the pbuffer, regardless of whether the + // exception originated in the user's GLEventListener. In + // these cases we immediately back off and use software + // rendering. + disableHardwareRendering(); + } + } else { + pbuffer.display(); + } + } else { + offscreenContext.invokeGL(displayAction, false, initAction); + } synchronized(semaphore) { - repaintDone = true; semaphore.notifyAll(); } } @@ -121,25 +188,74 @@ public final class GLJPanel extends JPanel implements GLDrawable { directly. */ public void reshape(int x, int y, int width, int height) { super.reshape(x, y, width, height); - // NOTE: we don't pay attention to the x and y provided since we - // are blitting into this component directly - final int fx = 0; - final int fy = 0; - final int fwidth = width; + + // Move all reshape requests onto AWT EventQueue thread + final int fx = x; + final int fy = y; + final int fwidth = width; final int fheight = height; - context.resizeOffscreenContext(width, height); - context.invokeGL(new Runnable() { + + Runnable r = new Runnable() { public void run() { - getGL().glViewport(fx, fy, fwidth, fheight); - drawableHelper.reshape(GLJPanel.this, fx, fy, fwidth, fheight); + GLContext context = null; + neededOffscreenImageWidth = 0; + neededOffscreenImageHeight = 0; + + if (!hardwareAccelerationDisabled) { + if (fwidth > pbufferWidth || fheight > pbufferHeight) { + // Must destroy and recreate pbuffer to fit + pbuffer.destroy(); + if (fwidth > pbufferWidth) { + pbufferWidth = getNextPowerOf2(fwidth); + } + if (fheight > pbufferHeight) { + pbufferHeight = getNextPowerOf2(fheight); + } + initialize(); + } + GLPbufferImpl pbufferImpl = (GLPbufferImpl) pbuffer; + context = pbufferImpl.getContext(); + // It looks like NVidia's drivers (at least the ones on my + // notebook) are buggy and don't allow a rectangle of less than + // the pbuffer's width to be read...this doesn't really matter + // because it's the Graphics.drawImage() calls that are the + // bottleneck. Should probably make the size of the offscreen + // image be the exact size of the pbuffer to save some work on + // resize operations... + neededOffscreenImageWidth = pbufferWidth; + neededOffscreenImageHeight = fheight; + } else { + offscreenContext.resizeOffscreenContext(fwidth, fheight); + context = offscreenContext; + neededOffscreenImageWidth = fwidth; + neededOffscreenImageHeight = fheight; + } + if (offscreenImage != null && - (offscreenImage.getWidth() != context.getOffscreenContextWidth() || - offscreenImage.getHeight() != context.getOffscreenContextHeight())) { + (offscreenImage.getWidth() != neededOffscreenImageWidth || + offscreenImage.getHeight() != neededOffscreenImageHeight)) { offscreenImage.flush(); offscreenImage = null; } + + panelWidth = fwidth; + panelHeight = fheight; + + context.invokeGL(new Runnable() { + public void run() { + getGL().glViewport(0, 0, panelWidth, panelHeight); + drawableHelper.reshape(GLJPanel.this, 0, 0, panelWidth, panelHeight); + } + }, true, initAction); } - }, true, initAction); + }; + if (EventQueue.isDispatchThread()) { + r.run(); + } else { + // Avoid blocking EventQueue thread due to possible deadlocks + // during component creation + EventQueue.invokeLater(r); + } } public void addGLEventListener(GLEventListener listener) { @@ -151,19 +267,35 @@ public final class GLJPanel extends JPanel implements GLDrawable { } public GL getGL() { - return context.getGL(); + if (!hardwareAccelerationDisabled) { + return pbuffer.getGL(); + } else { + return offscreenContext.getGL(); + } } public void setGL(GL gl) { - context.setGL(gl); + if (!hardwareAccelerationDisabled) { + pbuffer.setGL(gl); + } else { + offscreenContext.setGL(gl); + } } public GLU getGLU() { - return context.getGLU(); + if (!hardwareAccelerationDisabled) { + return pbuffer.getGLU(); + } else { + return offscreenContext.getGLU(); + } } public void setGLU(GLU glu) { - context.setGLU(glu); + if (!hardwareAccelerationDisabled) { + pbuffer.setGLU(glu); + } else { + offscreenContext.setGLU(glu); + } } public void setRenderingThread(Thread currentThreadOrNull) throws GLException { @@ -172,7 +304,7 @@ public final class GLJPanel extends JPanel implements GLDrawable { } public Thread getRenderingThread() { - return context.getRenderingThread(); + return null; } public void setNoAutoRedrawMode(boolean noAutoRedraws) { @@ -183,21 +315,32 @@ public final class GLJPanel extends JPanel implements GLDrawable { } public void setAutoSwapBufferMode(boolean onOrOff) { - context.setAutoSwapBufferMode(onOrOff); + if (!hardwareAccelerationDisabled) { + pbuffer.setAutoSwapBufferMode(onOrOff); + } else { + offscreenContext.setAutoSwapBufferMode(onOrOff); + } } public boolean getAutoSwapBufferMode() { - return context.getAutoSwapBufferMode(); + if (!hardwareAccelerationDisabled) { + return pbuffer.getAutoSwapBufferMode(); + } else { + return offscreenContext.getAutoSwapBufferMode(); + } } public void swapBuffers() { - context.invokeGL(swapBuffersAction, false, initAction); + if (!hardwareAccelerationDisabled) { + pbuffer.swapBuffers(); + } else { + offscreenContext.invokeGL(swapBuffersAction, false, initAction); + } } public boolean canCreateOffscreenDrawable() { - // For now let's say no; maybe we can reimplement this class in - // terms of pbuffers (though not all vendors support them, and - // they seem to require an onscreen context) + // For now let's say no, although we could using the heavyweight + // if hardware acceleration is still enabled return false; } @@ -208,119 +351,259 @@ public final class GLJPanel extends JPanel implements GLDrawable { } GLContext getContext() { - return context; + if (!hardwareAccelerationDisabled) { + return ((GLPbufferImpl) pbuffer).getContext(); + } else { + return offscreenContext; + } } //---------------------------------------------------------------------- // Internals only below this point // - class InitAction implements Runnable { - public void run() { - drawableHelper.init(GLJPanel.this); + private void disableHardwareRendering() { + if (Debug.verbose()) { + System.err.println("GLJPanel: Falling back on software rendering due to pbuffer problems"); } + hardwareAccelerationDisabled = true; + pbufferInitializationCompleted = false; + EventQueue.invokeLater(new Runnable() { + public void run() { + toplevel.setVisible(false); + // Should dispose of this -- not sure about stability on + // various cards -- should test (FIXME) + // toplevel.dispose(); + } + }); + initialize(); } - private InitAction initAction = new InitAction(); - - class DisplayAction implements Runnable { + + private void initialize() { + // Initialize either the hardware-accelerated rendering path or + // the lightweight rendering path + if (!hardwareAccelerationDisabled) { + boolean firstTime = false; + if (heavyweight == null) { + // Make the heavyweight share with the "shareWith" parameter. + // The pbuffer shares textures and display lists with the + // heavyweight, so by transitivity the pbuffer will share with + // it as well. + heavyweight = GLDrawableFactory.getFactory().createGLCanvas(new GLCapabilities(), shareWith); + firstTime = true; + } + if (heavyweight.canCreateOffscreenDrawable()) { + if (firstTime) { + toplevel = new Frame(); + toplevel.setUndecorated(true); + } + pbuffer = heavyweight.createOffscreenDrawable(offscreenCaps, pbufferWidth, pbufferHeight); + updater = new Updater(); + pbuffer.addGLEventListener(updater); + pbufferInitializationCompleted = false; + if (firstTime) { + toplevel.add(heavyweight); + toplevel.setSize(0, 0); + } + EventQueue.invokeLater(new Runnable() { + public void run() { + try { + toplevel.setVisible(true); + } catch (GLException e) { + disableHardwareRendering(); + } + } + }); + return; + } else { + // If the heavyweight reports that it can't create an + // offscreen drawable (pbuffer), don't try again the next + // time, and fall through to the software rendering path + hardwareAccelerationDisabled = true; + } + } + + // Create an offscreen context instead + offscreenContext = GLContextFactory.getFactory().createGLContext(null, offscreenCaps, chooser, + GLContextHelper.getContext(shareWith)); + offscreenContext.resizeOffscreenContext(panelWidth, panelHeight); + updater = new Updater(); + if (panelWidth > 0 && panelHeight > 0) { + offscreenContext.invokeGL(new Runnable() { + public void run() { + getGL().glViewport(0, 0, panelWidth, panelHeight); + drawableHelper.reshape(GLJPanel.this, 0, 0, panelWidth, panelHeight); + } + }, true, initAction); + } + } + + class Updater implements GLEventListener { private Graphics g; public void setGraphics(Graphics g) { this.g = g; } - public void run() { + public void init(GLDrawable drawable) { + if (!hardwareAccelerationDisabled) { + pbufferInitializationCompleted = true; + EventQueue.invokeLater(new Runnable() { + public void run() { + toplevel.setVisible(false); + } + }); + } + drawableHelper.init(GLJPanel.this); + } + + public void display(GLDrawable drawable) { drawableHelper.display(GLJPanel.this); + // Must now copy pixels from offscreen context into surface if (offscreenImage == null) { - int awtFormat = context.getOffscreenContextBufferedImageType(); - offscreenImage = new BufferedImage(context.getOffscreenContextWidth(), context.getOffscreenContextHeight(), awtFormat); - switch (awtFormat) { - case BufferedImage.TYPE_3BYTE_BGR: - glFormat = GL.GL_BGR; - glType = GL.GL_UNSIGNED_BYTE; - glComps = 3; - dbByte = (DataBufferByte) offscreenImage.getRaster().getDataBuffer(); - break; - - case BufferedImage.TYPE_INT_RGB: - glFormat = GL.GL_BGRA; - glType = GL.GL_UNSIGNED_BYTE; - glComps = 4; - dbInt = (DataBufferInt) offscreenImage.getRaster().getDataBuffer(); - break; - - case BufferedImage.TYPE_INT_ARGB: - glFormat = GL.GL_BGRA; - glType = context.getOffscreenContextPixelDataType(); - glComps = 4; - dbInt = (DataBufferInt) offscreenImage.getRaster().getDataBuffer(); - break; - - default: - // FIXME: Support more off-screen image types (current - // offscreen context implementations don't use others, and - // some of the OpenGL formats aren't supported in the 1.1 - // headers, which we're currently using) - throw new GLException("Unsupported offscreen image type " + awtFormat); + if (panelWidth > 0 && panelHeight > 0) { + // It looks like NVidia's drivers (at least the ones on my + // notebook) are buggy and don't allow a sub-rectangle to be + // read from a pbuffer...this doesn't really matter because + // it's the Graphics.drawImage() calls that are the + // bottleneck + + int awtFormat = 0; + int hwGLFormat = 0; + if (!hardwareAccelerationDisabled) { + // Should be more flexible in these BufferedImage formats; + // perhaps see what the preferred image types are on the + // given platform + if (offscreenCaps.getAlphaBits() > 0) { + awtFormat = BufferedImage.TYPE_INT_ARGB; + } else { + awtFormat = BufferedImage.TYPE_INT_RGB; + } + + // This seems to be a good choice on all platforms + hwGLFormat = GL.GL_UNSIGNED_INT_8_8_8_8_REV; + } else { + awtFormat = offscreenContext.getOffscreenContextBufferedImageType(); + } + + offscreenImage = new BufferedImage(neededOffscreenImageWidth, + neededOffscreenImageHeight, + awtFormat); + switch (awtFormat) { + case BufferedImage.TYPE_3BYTE_BGR: + glFormat = GL.GL_BGR; + glType = GL.GL_UNSIGNED_BYTE; + dbByte = (DataBufferByte) offscreenImage.getRaster().getDataBuffer(); + break; + + case BufferedImage.TYPE_INT_RGB: + case BufferedImage.TYPE_INT_ARGB: + glFormat = GL.GL_BGRA; + glType = (hardwareAccelerationDisabled + ? offscreenContext.getOffscreenContextPixelDataType() + : hwGLFormat); + dbInt = (DataBufferInt) offscreenImage.getRaster().getDataBuffer(); + break; + + default: + // FIXME: Support more off-screen image types (current + // offscreen context implementations don't use others, and + // some of the OpenGL formats aren't supported in the 1.1 + // headers, which we're currently using) + throw new GLException("Unsupported offscreen image type " + awtFormat); + } } } - GL gl = getGL(); - // Save current modes - gl.glGetIntegerv(GL.GL_PACK_SWAP_BYTES, swapbytes); - gl.glGetIntegerv(GL.GL_PACK_LSB_FIRST, lsbfirst); - gl.glGetIntegerv(GL.GL_PACK_ROW_LENGTH, rowlength); - gl.glGetIntegerv(GL.GL_PACK_SKIP_ROWS, skiprows); - gl.glGetIntegerv(GL.GL_PACK_SKIP_PIXELS, skippixels); - gl.glGetIntegerv(GL.GL_PACK_ALIGNMENT, alignment); - - // Little endian machines (DEC Alpha, Intel X86, PPC (in LSB - // mode)... for example) could benefit from setting - // GL_PACK_LSB_FIRST to GL_TRUE instead of GL_FALSE, but this - // would require changing the generated bitmaps too. - gl.glPixelStorei(GL.GL_PACK_SWAP_BYTES, GL.GL_FALSE); - gl.glPixelStorei(GL.GL_PACK_LSB_FIRST, GL.GL_TRUE); - gl.glPixelStorei(GL.GL_PACK_ROW_LENGTH, offscreenImage.getWidth()); - gl.glPixelStorei(GL.GL_PACK_SKIP_ROWS, 0); - gl.glPixelStorei(GL.GL_PACK_SKIP_PIXELS, 0); - gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, 1); - - // Actually read the pixels. - gl.glReadBuffer(context.getOffscreenContextReadBuffer()); - if (dbByte != null) { - gl.glReadPixels(0, 0, offscreenImage.getWidth(), offscreenImage.getHeight(), glFormat, glType, dbByte.getData()); - } else if (dbInt != null) { - gl.glReadPixels(0, 0, offscreenImage.getWidth(), offscreenImage.getHeight(), glFormat, glType, dbInt.getData()); - } + if (offscreenImage != null) { + GL gl = getGL(); + // Save current modes + gl.glGetIntegerv(GL.GL_PACK_SWAP_BYTES, swapbytes); + gl.glGetIntegerv(GL.GL_PACK_ROW_LENGTH, rowlength); + gl.glGetIntegerv(GL.GL_PACK_SKIP_ROWS, skiprows); + gl.glGetIntegerv(GL.GL_PACK_SKIP_PIXELS, skippixels); + gl.glGetIntegerv(GL.GL_PACK_ALIGNMENT, alignment); + + gl.glPixelStorei(GL.GL_PACK_SWAP_BYTES, GL.GL_FALSE); + gl.glPixelStorei(GL.GL_PACK_ROW_LENGTH, offscreenImage.getWidth()); + gl.glPixelStorei(GL.GL_PACK_SKIP_ROWS, 0); + gl.glPixelStorei(GL.GL_PACK_SKIP_PIXELS, 0); + gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, 1); + + // Actually read the pixels. + gl.glReadBuffer(GL.GL_FRONT); + if (dbByte != null) { + gl.glReadPixels(0, 0, offscreenImage.getWidth(), offscreenImage.getHeight(), glFormat, glType, dbByte.getData()); + } else if (dbInt != null) { + gl.glReadPixels(0, 0, offscreenImage.getWidth(), offscreenImage.getHeight(), glFormat, glType, dbInt.getData()); + } - // Restore saved modes. - gl.glPixelStorei(GL.GL_PACK_SWAP_BYTES, swapbytes[0]); - gl.glPixelStorei(GL.GL_PACK_LSB_FIRST, lsbfirst[0]); - gl.glPixelStorei(GL.GL_PACK_ROW_LENGTH, rowlength[0]); - gl.glPixelStorei(GL.GL_PACK_SKIP_ROWS, skiprows[0]); - gl.glPixelStorei(GL.GL_PACK_SKIP_PIXELS, skippixels[0]); - gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, alignment[0]); + // Restore saved modes. + gl.glPixelStorei(GL.GL_PACK_SWAP_BYTES, swapbytes[0]); + gl.glPixelStorei(GL.GL_PACK_ROW_LENGTH, rowlength[0]); + gl.glPixelStorei(GL.GL_PACK_SKIP_ROWS, skiprows[0]); + gl.glPixelStorei(GL.GL_PACK_SKIP_PIXELS, skippixels[0]); + gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, alignment[0]); - gl.glFlush(); - gl.glFinish(); - - if (context.offscreenImageNeedsVerticalFlip()) { - g.drawImage(offscreenImage, - 0, 0, offscreenImage.getWidth(), offscreenImage.getHeight(), - 0, offscreenImage.getHeight(), offscreenImage.getWidth(), 0, - GLJPanel.this); - } else { - g.drawImage(offscreenImage, 0, 0, offscreenImage.getWidth(), offscreenImage.getHeight(), GLJPanel.this); + if (!hardwareAccelerationDisabled || + offscreenContext.offscreenImageNeedsVerticalFlip()) { + // This performs reasonably well; the snippet below does not. + // Should figure out if we need to set the image scaling + // preference to FAST since it doesn't require subsampling + // of pixels -- FIXME + for (int i = 0; i < panelHeight - 1; i++) { + g.drawImage(offscreenImage, + 0, i, panelWidth, i+1, + 0, panelHeight - i - 2, panelWidth, panelHeight - i - 1, + GLJPanel.this); + } + } else { + g.drawImage(offscreenImage, 0, 0, offscreenImage.getWidth(), offscreenImage.getHeight(), GLJPanel.this); + } } } + + public void reshape(GLDrawable drawable, int x, int y, int width, int height) { + // This is handled above and dispatched directly to the appropriate context + } + + public void displayChanged(GLDrawable drawable, boolean modeChanged, boolean deviceChanged) { + } + } + + class InitAction implements Runnable { + public void run() { + updater.init(GLJPanel.this); + } + } + private InitAction initAction = new InitAction(); + + class DisplayAction implements Runnable { + public void run() { + updater.display(GLJPanel.this); + } } private DisplayAction displayAction = new DisplayAction(); + // This one is used exclusively in the non-hardware-accelerated case class SwapBuffersAction implements Runnable { public void run() { - context.swapBuffers(); + offscreenContext.swapBuffers(); } } private SwapBuffersAction swapBuffersAction = new SwapBuffersAction(); + + private int getNextPowerOf2(int number) { + if (((number-1) & number) == 0) { + //ex: 8 -> 0b1000; 8-1=7 -> 0b0111; 0b1000&0b0111 == 0 + return number; + } + int power = 0; + while (number > 0) { + number = number>>1; + power++; + } + return (1<<power); + } } |