diff options
-rw-r--r-- | src/classes/javax/media/opengl/awt/GLJPanel.java | 1585 |
1 files changed, 850 insertions, 735 deletions
diff --git a/src/classes/javax/media/opengl/awt/GLJPanel.java b/src/classes/javax/media/opengl/awt/GLJPanel.java index a602f1f22..c5877e5be 100644 --- a/src/classes/javax/media/opengl/awt/GLJPanel.java +++ b/src/classes/javax/media/opengl/awt/GLJPanel.java @@ -40,7 +40,6 @@ package javax.media.opengl.awt; import javax.media.opengl.*; -import javax.media.opengl.awt.*; import java.awt.*; import java.awt.geom.*; @@ -89,45 +88,40 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { private GLDrawableHelper drawableHelper = new GLDrawableHelper(); private volatile boolean isInitialized; - private volatile boolean shouldInitialize = false; // Data used for either pbuffers or pixmap-based offscreen surfaces private GLCapabilities offscreenCaps; private GLCapabilitiesChooser chooser; private GLContext shareWith; - // This image is exactly the correct size to render into the panel - private BufferedImage offscreenImage; - // One of these is used to store the read back pixels before storing - // in the BufferedImage - private ByteBuffer readBackBytes; - private IntBuffer readBackInts; - private int readBackWidthInPixels; - private int readBackHeightInPixels; // Width of the actual GLJPanel private int panelWidth = 0; private int panelHeight = 0; - private Updater updater; - private int awtFormat; - private int glFormat; - private int glType; // Lazy reshape notification private boolean handleReshape = false; private boolean sendReshape = true; private static GLDrawableFactoryImpl factory; - // Implementation using pbuffers + // The backend in use + private Backend backend; + + // Used by all backends either directly or indirectly to hook up callbacks + private Updater updater = new Updater(); + + // Turns off the pbuffer-based backend (used by default, unless the + // Java 2D / OpenGL pipeline is in use) private static boolean hardwareAccelerationDisabled = Debug.isPropertyDefined("jogl.gljpanel.nohw"); + + // Turns off the fallback to software-based rendering from + // pbuffer-based rendering private static boolean softwareRenderingDisabled = Debug.isPropertyDefined("jogl.gljpanel.nosw"); - private GLPbuffer pbuffer; - private int pbufferWidth = 256; - private int pbufferHeight = 256; - // Implementation using software rendering - private GLDrawableImpl offscreenDrawable; - private GLContextImpl offscreenContext; + // Indicates whether the Java 2D OpenGL pipeline is enabled + private boolean oglPipelineEnabled = + Java2D.isOGLPipelineActive() && + !Debug.isPropertyDefined("jogl.gljpanel.noogl"); // For handling reshape events lazily private int reshapeX; @@ -135,70 +129,10 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { private int reshapeWidth; private int reshapeHeight; - // For saving/restoring of OpenGL state during ReadPixels - private int[] swapbytes = new int[1]; - private int[] rowlength = new int[1]; - private int[] skiprows = new int[1]; - private int[] skippixels = new int[1]; - private int[] alignment = new int[1]; - - // Implementation using Java2D OpenGL pipeline's back buffer - private boolean oglPipelineEnabled = - Java2D.isOGLPipelineActive() && - !Debug.isPropertyDefined("jogl.gljpanel.noogl"); - // Opaque Object identifier representing the Java2D surface we are - // drawing to; used to determine when to destroy and recreate JOGL - // context - private Object j2dSurface; - // Graphics object being used during Java2D update action - // (absolutely essential to cache this) - private Graphics cached2DGraphics; - // No-op context representing the Java2D OpenGL context - private GLContext j2dContext; - // Context associated with no-op drawable representing the JOGL - // OpenGL context - private GLDrawable joglDrawable; - // The real OpenGL context JOGL uses to render - private GLContext joglContext; - // State captured from Java2D OpenGL context necessary in order to - // properly render into Java2D back buffer - private int[] drawBuffer = new int[1]; - private int[] readBuffer = new int[1]; - // This is required when the FBO option of the Java2D / OpenGL - // pipeline is active - private int[] frameBuffer = new int[1]; - // Current (as of this writing) NVidia drivers have a couple of bugs - // relating to the sharing of framebuffer and renderbuffer objects - // between contexts. It appears we have to (a) reattach the color - // attachment and (b) actually create new depth buffer storage and - // attach it in order for the FBO to behave properly in our context. - private boolean checkedForFBObjectWorkarounds; - private boolean fbObjectWorkarounds; - private int[] frameBufferDepthBuffer; - private int[] frameBufferTexture; - private boolean createNewDepthBuffer; - // Current (as of this writing) ATI drivers have problems when the - // same FBO is bound in two different contexts. Here we check for - // this case and explicitly release the FBO from Java2D's context - // before switching to ours. Java2D will re-bind the FBO when it - // makes its context current the next time. Interestingly, if we run - // this code path on NVidia hardware, it breaks the rendering - // results -- no output is generated. This doesn't appear to be an - // interaction with the abovementioned NVidia-specific workarounds, - // as even if we disable that code the FBO is still reported as - // incomplete in our context. - private boolean checkedGLVendor; - private boolean vendorIsATI; - - // Holding on to this GraphicsConfiguration is a workaround for a - // problem in the Java 2D / JOGL bridge when FBOs are enabled; see - // comment related to Issue 274 below - private GraphicsConfiguration workaroundConfig; - // These are always set to (0, 0) except when the Java2D / OpenGL // pipeline is active - private int viewportX; - private int viewportY; + private int viewportX; + private int viewportY; static { // Force eager initialization of part of the Java2D class since @@ -269,200 +203,6 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { } } - private void captureJ2DState(GL2 gl, Graphics g) { - gl.glGetIntegerv(GL2.GL_DRAW_BUFFER, drawBuffer, 0); - gl.glGetIntegerv(GL2.GL_READ_BUFFER, readBuffer, 0); - if (Java2D.isFBOEnabled() && - Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) { - if (DEBUG && VERBOSE) { - System.err.println("GLJPanel: Fetching GL_FRAMEBUFFER_BINDING"); - } - gl.glGetIntegerv(GL2.GL_FRAMEBUFFER_BINDING, frameBuffer, 0); - - if (fbObjectWorkarounds || - !checkedForFBObjectWorkarounds) { - // See above for description of what we are doing here - if (frameBufferTexture == null) - frameBufferTexture = new int[1]; - - // Query the framebuffer for its color buffer so we can hook - // it back up in our context (should not be necessary) - gl.glGetFramebufferAttachmentParameteriv(GL2.GL_FRAMEBUFFER, - GL2.GL_COLOR_ATTACHMENT0, - GL2.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, - frameBufferTexture, 0); - if (DEBUG && VERBOSE) { - System.err.println("GLJPanel: FBO COLOR_ATTACHMENT0: " + frameBufferTexture[0]); - } - } - - if (!checkedGLVendor) { - checkedGLVendor = true; - String vendor = gl.glGetString(GL2.GL_VENDOR); - - if ((vendor != null) && - vendor.startsWith("ATI")) { - vendorIsATI = true; - } - } - - if (vendorIsATI) { - // Unbind the FBO from Java2D's context as it appears that - // driver bugs on ATI's side are causing problems if the FBO is - // simultaneously bound to more than one context. Java2D will - // re-bind the FBO during the next validation of its context. - // Note: this breaks rendering at least on NVidia hardware - gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, 0); - } - } - } - - private boolean preGL(Graphics g) { - GL2 gl = joglContext.getGL().getGL2(); - // Set up needed state in JOGL context from Java2D context - gl.glEnable(GL2.GL_SCISSOR_TEST); - Rectangle r = Java2D.getOGLScissorBox(g); - - if (r == null) { - if (DEBUG && VERBOSE) { - System.err.println("Java2D.getOGLScissorBox() returned null"); - } - return false; - } - if (DEBUG && VERBOSE) { - System.err.println("GLJPanel: gl.glScissor(" + r.x + ", " + r.y + ", " + r.width + ", " + r.height + ")"); - } - - gl.glScissor(r.x, r.y, r.width, r.height); - Rectangle oglViewport = Java2D.getOGLViewport(g, panelWidth, panelHeight); - // If the viewport X or Y changes, in addition to the panel's - // width or height, we need to send a reshape operation to the - // client - if ((viewportX != oglViewport.x) || - (viewportY != oglViewport.y)) { - sendReshape = true; - if (DEBUG) { - System.err.println("Sending reshape because viewport changed"); - System.err.println(" viewportX (" + viewportX + ") ?= oglViewport.x (" + oglViewport.x + ")"); - System.err.println(" viewportY (" + viewportY + ") ?= oglViewport.y (" + oglViewport.y + ")"); - } - } - viewportX = oglViewport.x; - viewportY = oglViewport.y; - - // If the FBO option is active, bind to the FBO from the Java2D - // context. - // Note that all of the plumbing in the context sharing stuff will - // allow us to bind to this object since it's in our namespace. - if (Java2D.isFBOEnabled() && - Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) { - if (DEBUG && VERBOSE) { - System.err.println("GLJPanel: Binding to framebuffer object " + frameBuffer[0]); - } - - // The texture target for Java2D's OpenGL pipeline when using FBOs - // -- either GL_TEXTURE_2D or GL_TEXTURE_RECTANGLE - int fboTextureTarget = Java2D.getOGLTextureType(g); - - if (!checkedForFBObjectWorkarounds) { - checkedForFBObjectWorkarounds = true; - gl.glBindTexture(fboTextureTarget, 0); - gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, frameBuffer[0]); - if (gl.glCheckFramebufferStatus(GL2.GL_FRAMEBUFFER) != - GL2.GL_FRAMEBUFFER_COMPLETE) { - // Need to do workarounds - fbObjectWorkarounds = true; - createNewDepthBuffer = true; - if (DEBUG) { - System.err.println("-- GLJPanel: discovered frame_buffer_object workarounds to be necessary"); - } - } else { - // Don't need the frameBufferTexture temporary any more - frameBufferTexture = null; - } - } - - if (fbObjectWorkarounds && createNewDepthBuffer) { - if (frameBufferDepthBuffer == null) - frameBufferDepthBuffer = new int[1]; - - // Create our own depth renderbuffer and associated storage - // If we have an old one, delete it - if (frameBufferDepthBuffer[0] != 0) { - gl.glDeleteRenderbuffers(1, frameBufferDepthBuffer, 0); - frameBufferDepthBuffer[0] = 0; - } - - gl.glBindTexture(fboTextureTarget, frameBufferTexture[0]); - int[] width = new int[1]; - int[] height = new int[1]; - gl.glGetTexLevelParameteriv(fboTextureTarget, 0, GL2.GL_TEXTURE_WIDTH, width, 0); - gl.glGetTexLevelParameteriv(fboTextureTarget, 0, GL2.GL_TEXTURE_HEIGHT, height, 0); - - gl.glGenRenderbuffers(1, frameBufferDepthBuffer, 0); - if (DEBUG) { - System.err.println("GLJPanel: Generated frameBufferDepthBuffer " + frameBufferDepthBuffer[0] + - " with width " + width[0] + ", height " + height[0]); - } - - gl.glBindRenderbuffer(GL2.GL_RENDERBUFFER, frameBufferDepthBuffer[0]); - // FIXME: may need a loop here like in Java2D - gl.glRenderbufferStorage(GL2.GL_RENDERBUFFER, GL2.GL_DEPTH_COMPONENT24, width[0], height[0]); - - gl.glBindRenderbuffer(GL2.GL_RENDERBUFFER, 0); - createNewDepthBuffer = false; - } - - gl.glBindTexture(fboTextureTarget, 0); - gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, frameBuffer[0]); - - if (fbObjectWorkarounds) { - // Hook up the color and depth buffer attachment points for this framebuffer - gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, - GL2.GL_COLOR_ATTACHMENT0, - fboTextureTarget, - frameBufferTexture[0], - 0); - if (DEBUG && VERBOSE) { - System.err.println("GLJPanel: frameBufferDepthBuffer: " + frameBufferDepthBuffer[0]); - } - gl.glFramebufferRenderbuffer(GL2.GL_FRAMEBUFFER, - GL2.GL_DEPTH_ATTACHMENT, - GL2.GL_RENDERBUFFER, - frameBufferDepthBuffer[0]); - } - - if (DEBUG) { - int status = gl.glCheckFramebufferStatus(GL2.GL_FRAMEBUFFER); - if (status != GL2.GL_FRAMEBUFFER_COMPLETE) { - throw new GLException("Error: framebuffer was incomplete: status = 0x" + - Integer.toHexString(status)); - } - } - } else { - if (DEBUG && VERBOSE) { - System.err.println("GLJPanel: Setting up drawBuffer " + drawBuffer[0] + - " and readBuffer " + readBuffer[0]); - } - - gl.glDrawBuffer(drawBuffer[0]); - gl.glReadBuffer(readBuffer[0]); - } - - return true; - } - - private void postGL(Graphics g) { - if (Java2D.isFBOEnabled() && - Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) { - // Unbind the framebuffer from our context to work around - // apparent driver bugs or at least unspecified behavior causing - // OpenGL to run out of memory with certain cards and drivers - GL2 gl = joglContext.getGL().getGL2(); - gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, 0); - } - } - /** Overridden to cause OpenGL rendering to be performed during repaint cycles. Subclasses which override this method must call super.paintComponent() in their paintComponent() method in order @@ -492,8 +232,8 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { return; } - if (shouldInitialize) { - initialize(); + if (backend == null || !isInitialized) { + createAndInitializeBackend(); } if (!isInitialized) { @@ -509,171 +249,7 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { } updater.setGraphics(g); - - if (oglPipelineEnabled) { - - // This is a workaround for an issue in the Java 2D / JOGL - // bridge (reported by an end user as JOGL Issue 274) where Java - // 2D can occasionally leave its internal OpenGL context current - // to the on-screen window rather than its internal "scratch" - // pbuffer surface to which the FBO is attached. JOGL expects to - // find a stable OpenGL drawable (on Windows, an HDC) upon which - // it can create another OpenGL context. It turns out that, on - // Windows, when Java 2D makes its internal OpenGL context - // current against the window in order to put pixels on the - // screen, it gets the device context for the window, makes its - // context current, and releases the device context. This means - // that when JOGL's Runnable gets to run below, the HDC is - // already invalid. The workaround for this is to force Java 2D - // to make its context current to the scratch surface, which we - // can do by executing an empty Runnable with the "shared" - // context current. This will be fixed in a Java SE 6 update - // release, hopefully 6u2. - if (Java2D.isFBOEnabled()) { - if (workaroundConfig == null) { - workaroundConfig = GraphicsEnvironment. - getLocalGraphicsEnvironment(). - getDefaultScreenDevice(). - getDefaultConfiguration(); - } - Java2D.invokeWithOGLSharedContextCurrent(workaroundConfig, new Runnable() { public void run() {}}); - } - - Java2D.invokeWithOGLContextCurrent(g, new Runnable() { - public void run() { - if (DEBUG && VERBOSE) { - System.err.println("-- In invokeWithOGLContextCurrent"); - } - - // Create no-op context representing Java2D context - if (j2dContext == null) { - j2dContext = factory.createExternalGLContext(); - if (DEBUG) { - j2dContext.setGL(new DebugGL2(j2dContext.getGL().getGL2())); - } - - // Check to see whether we can support the requested - // capabilities or need to fall back to a pbuffer - // FIXME: add more checks? - - j2dContext.makeCurrent(); - GL2 gl = j2dContext.getGL().getGL2(); - if ((getGLInteger(gl, GL2.GL_RED_BITS) < offscreenCaps.getRedBits()) || - (getGLInteger(gl, GL2.GL_GREEN_BITS) < offscreenCaps.getGreenBits()) || - (getGLInteger(gl, GL2.GL_BLUE_BITS) < offscreenCaps.getBlueBits()) || - // (getGLInteger(gl, GL2.GL_ALPHA_BITS) < offscreenCaps.getAlphaBits()) || - (getGLInteger(gl, GL2.GL_ACCUM_RED_BITS) < offscreenCaps.getAccumRedBits()) || - (getGLInteger(gl, GL2.GL_ACCUM_GREEN_BITS) < offscreenCaps.getAccumGreenBits()) || - (getGLInteger(gl, GL2.GL_ACCUM_BLUE_BITS) < offscreenCaps.getAccumBlueBits()) || - (getGLInteger(gl, GL2.GL_ACCUM_ALPHA_BITS) < offscreenCaps.getAccumAlphaBits()) || - // (getGLInteger(gl, GL2.GL_DEPTH_BITS) < offscreenCaps.getDepthBits()) || - (getGLInteger(gl, GL2.GL_STENCIL_BITS) < offscreenCaps.getStencilBits())) { - if (DEBUG) { - System.err.println("GLJPanel: Falling back to pbuffer-based support because Java2D context insufficient"); - System.err.println(" Available Required"); - System.err.println("GL_RED_BITS " + getGLInteger(gl, GL2.GL_RED_BITS) + " " + offscreenCaps.getRedBits()); - System.err.println("GL_GREEN_BITS " + getGLInteger(gl, GL2.GL_GREEN_BITS) + " " + offscreenCaps.getGreenBits()); - System.err.println("GL_BLUE_BITS " + getGLInteger(gl, GL2.GL_BLUE_BITS) + " " + offscreenCaps.getBlueBits()); - System.err.println("GL_ALPHA_BITS " + getGLInteger(gl, GL2.GL_ALPHA_BITS) + " " + offscreenCaps.getAlphaBits()); - System.err.println("GL_ACCUM_RED_BITS " + getGLInteger(gl, GL2.GL_ACCUM_RED_BITS) + " " + offscreenCaps.getAccumRedBits()); - System.err.println("GL_ACCUM_GREEN_BITS " + getGLInteger(gl, GL2.GL_ACCUM_GREEN_BITS) + " " + offscreenCaps.getAccumGreenBits()); - System.err.println("GL_ACCUM_BLUE_BITS " + getGLInteger(gl, GL2.GL_ACCUM_BLUE_BITS) + " " + offscreenCaps.getAccumBlueBits()); - System.err.println("GL_ACCUM_ALPHA_BITS " + getGLInteger(gl, GL2.GL_ACCUM_ALPHA_BITS) + " " + offscreenCaps.getAccumAlphaBits()); - System.err.println("GL_DEPTH_BITS " + getGLInteger(gl, GL2.GL_DEPTH_BITS) + " " + offscreenCaps.getDepthBits()); - System.err.println("GL_STENCIL_BITS " + getGLInteger(gl, GL2.GL_STENCIL_BITS) + " " + offscreenCaps.getStencilBits()); - } - isInitialized = false; - shouldInitialize = true; - oglPipelineEnabled = false; - handleReshape = true; - j2dContext.release(); - j2dContext.destroy(); - j2dContext = null; - return; - } - j2dContext.release(); - } - - j2dContext.makeCurrent(); - try { - captureJ2DState(j2dContext.getGL().getGL2(), g); - Object curSurface = Java2D.getOGLSurfaceIdentifier(g); - if (curSurface != null) { - if (j2dSurface != curSurface) { - if (joglContext != null) { - joglContext.destroy(); - joglContext = null; - joglDrawable = null; - sendReshape = true; - if (DEBUG) { - System.err.println("Sending reshape because surface changed"); - System.err.println("New surface = " + curSurface); - } - } - j2dSurface = curSurface; - } - if (joglContext == null) { - if (factory.canCreateExternalGLDrawable()) { - joglDrawable = factory.createExternalGLDrawable(); - joglContext = joglDrawable.createContext(shareWith); - } else if (factory.canCreateContextOnJava2DSurface()) { - // Mac OS X code path - joglContext = factory.createContextOnJava2DSurface(g, shareWith); - } - if (DEBUG) { - joglContext.setGL(new DebugGL2(joglContext.getGL().getGL2())); - } - - if (Java2D.isFBOEnabled() && - Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT && - fbObjectWorkarounds) { - createNewDepthBuffer = true; - } - } - if (joglContext instanceof Java2DGLContext) { - // Mac OS X code path - ((Java2DGLContext) joglContext).setGraphics(g); - } - - if (DEBUG && VERBOSE && Java2D.isFBOEnabled()) { - System.err.print("-- Surface type: "); - int surfaceType = Java2D.getOGLSurfaceType(g); - if (surfaceType == Java2D.UNDEFINED) { - System.err.println("UNDEFINED"); - } else if (surfaceType == Java2D.WINDOW) { - System.err.println("WINDOW"); - } else if (surfaceType == Java2D.PBUFFER) { - System.err.println("PBUFFER"); - } else if (surfaceType == Java2D.TEXTURE) { - System.err.println("TEXTURE"); - } else if (surfaceType == Java2D.FLIP_BACKBUFFER) { - System.err.println("FLIP_BACKBUFFER"); - } else if (surfaceType == Java2D.FBOBJECT) { - System.err.println("FBOBJECT"); - } else { - System.err.println("(Unknown surface type " + surfaceType + ")"); - } - } - - drawableHelper.invokeGL(joglDrawable, joglContext, displayAction, initAction); - } - } finally { - j2dContext.release(); - } - } - }); - } else { - if (!hardwareAccelerationDisabled) { - pbuffer.display(); - } else { - drawableHelper.invokeGL(offscreenDrawable, offscreenContext, displayAction, initAction); - } - - if (offscreenImage != null) { - // Draw resulting image in one shot - g.drawImage(offscreenImage, 0, 0, offscreenImage.getWidth(), offscreenImage.getHeight(), this); - } - } + backend.doPaintComponent(g); } /** Overridden to track when this component is added to a container. @@ -685,7 +261,6 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { <DL><DD><CODE>addNotify</CODE> in class <CODE>java.awt.Component</CODE></DD></DL> */ public void addNotify() { super.addNotify(); - shouldInitialize = true; if (DEBUG) { System.err.println("GLJPanel.addNotify()"); } @@ -702,36 +277,9 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { if (DEBUG) { System.err.println("GLJPanel.removeNotify()"); } - if (oglPipelineEnabled) { - Java2D.invokeWithOGLContextCurrent(null, new Runnable() { - public void run() { - if (joglContext != null) { - joglContext.destroy(); - joglContext = null; - } - joglDrawable = null; - if (j2dContext != null) { - j2dContext.destroy(); - j2dContext = null; - } - } - }); - } else { - if (!hardwareAccelerationDisabled) { - if (pbuffer != null) { - pbuffer.destroy(); - pbuffer = null; - } - } else { - if (offscreenContext != null) { - offscreenContext.destroy(); - offscreenContext = null; - } - if (offscreenDrawable != null) { - offscreenDrawable.destroy(); - offscreenDrawable = null; - } - } + if (backend != null) { + backend.destroy(); + backend = null; } isInitialized = false; super.removeNotify(); @@ -755,11 +303,8 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { } public void setOpaque(boolean opaque) { - if (opaque != isOpaque()) { - if (offscreenImage != null) { - offscreenImage.flush(); - offscreenImage = null; - } + if (backend != null) { + backend.setOpaque(opaque); } super.setOpaque(opaque); } @@ -773,30 +318,17 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { } public GLContext createContext(GLContext shareWith) { - if (!hardwareAccelerationDisabled) { - return pbuffer.createContext(shareWith); - } else { - return offscreenDrawable.createContext(shareWith); - } + return backend.createContext(shareWith); } public void setRealized(boolean realized) { } public GLContext getContext() { - if (oglPipelineEnabled) { - return joglContext; - } else { - if (!hardwareAccelerationDisabled) { - // Workaround for crashes in NetBeans GUI builder - if (pbuffer == null && Beans.isDesignTime()) { - return null; - } - return pbuffer.getContext(); - } else { - return offscreenContext; - } + if (backend == null) { + return null; } + return backend.getContext(); } public GL getGL() { @@ -805,9 +337,6 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { } public void setGL(GL gl) { - if(!gl.isGL2()) { - throw new GLException("not a GL2 implementation"); - } GLContext context = getContext(); if (context != null) { context.setGL(gl); @@ -815,39 +344,28 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { } public void setAutoSwapBufferMode(boolean onOrOff) { - if (!hardwareAccelerationDisabled) { - // Workaround for crashes in NetBeans GUI builder - if (pbuffer == null && Beans.isDesignTime()) { - return; - } - pbuffer.setAutoSwapBufferMode(onOrOff); - } else { - drawableHelper.setAutoSwapBufferMode(onOrOff); - } + // In the current implementation this is a no-op. Both the pbuffer + // and pixmap based rendering paths use a single-buffered surface + // so swapping the buffers doesn't do anything. We also don't + // currently have the provision to skip copying the data to the + // Swing portion of the GLJPanel in any of the rendering paths. } public boolean getAutoSwapBufferMode() { - if (!hardwareAccelerationDisabled && !oglPipelineEnabled) { - return pbuffer.getAutoSwapBufferMode(); - } else { - return drawableHelper.getAutoSwapBufferMode(); - } + // In the current implementation this is a no-op. Both the pbuffer + // and pixmap based rendering paths use a single-buffered surface + // so swapping the buffers doesn't do anything. We also don't + // currently have the provision to skip copying the data to the + // Swing portion of the GLJPanel in any of the rendering paths. + return true; } public void swapBuffers() { - // In the current implementation this is basically a no-op. Both - // the pbuffer and pixmap based rendering paths use a single- - // buffered surface so swapping the buffers doesn't do anything. - // We also don't currently have the provision to skip copying the - // data to the Swing portion of the GLJPanel in any of the - // rendering paths. - if (oglPipelineEnabled) { - // Do nothing - } else if (!hardwareAccelerationDisabled) { - pbuffer.swapBuffers(); - } else { - drawableHelper.invokeGL(offscreenDrawable, offscreenContext, swapBuffersAction, initAction); - } + // In the current implementation this is a no-op. Both the pbuffer + // and pixmap based rendering paths use a single-buffered surface + // so swapping the buffers doesn't do anything. We also don't + // currently have the provision to skip copying the data to the + // Swing portion of the GLJPanel in any of the rendering paths. } /** For a translucent GLJPanel (one for which {@link #setOpaque @@ -866,25 +384,8 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { return oglPipelineEnabled; } - protected GLCapabilities caps=null; - public GLCapabilities getChosenGLCapabilities() { - if (oglPipelineEnabled) { - if(caps==null) { - caps = new GLCapabilities(); - } - return caps; - } - - if (hardwareAccelerationDisabled) { - if (offscreenDrawable != null) - return offscreenDrawable.getChosenGLCapabilities(); - } else { - if (pbuffer != null) - return pbuffer.getChosenGLCapabilities(); - } - - return null; + return backend.getChosenGLCapabilities(); } public NativeWindow getNativeWindow() { @@ -912,7 +413,7 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { // Internals only below this point // - private void initialize() { + private void createAndInitializeBackend() { if (panelWidth == 0 || panelHeight == 0) { // See whether we have a non-zero size yet and can go ahead with @@ -929,63 +430,32 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { panelHeight = reshapeHeight; } - if (!oglPipelineEnabled) { - // Initialize either the hardware-accelerated rendering path or - // the lightweight rendering path - if (!hardwareAccelerationDisabled) { - if (factory.canCreateGLPbuffer()) { - if (pbuffer != null) { - throw new InternalError("Creating pbuffer twice without destroying it (memory leak / correctness bug)"); - } - try { - pbuffer = factory.createGLPbuffer(offscreenCaps, - null, - pbufferWidth, - pbufferHeight, - shareWith); - updater = new Updater(); - pbuffer.addGLEventListener(updater); - shouldInitialize = false; - isInitialized = true; - return; - } catch (GLException e) { - if (DEBUG) { - e.printStackTrace(); - System.err.println("GLJPanel: Falling back on software rendering because of problems creating pbuffer"); - } - hardwareAccelerationDisabled = true; - } + do { + if (backend == null) { + if (oglPipelineEnabled) { + backend = new J2DOGLBackend(); } else { - if (DEBUG) { - System.err.println("GLJPanel: Falling back on software rendering because no pbuffer support"); + if (!hardwareAccelerationDisabled && + factory.canCreateGLPbuffer()) { + backend = new PbufferBackend(); + } else { + if (softwareRenderingDisabled) { + throw new GLException("Fallback to software rendering disabled by user"); + } + backend = new SoftwareBackend(); } - - // If the factory reports that it can't create a pbuffer, - // don't try again the next time, and fall through to the - // software rendering path - hardwareAccelerationDisabled = true; } } - if (softwareRenderingDisabled) { - throw new GLException("Fallback to software rendering disabled by user"); + if (!isInitialized) { + backend.initialize(); } - - // Fall-through path: create an offscreen context instead - offscreenDrawable = factory.createOffscreenDrawable(offscreenCaps, chooser); - offscreenDrawable.setSize(Math.max(1, panelWidth), Math.max(1, panelHeight)); - offscreenContext = (GLContextImpl) offscreenDrawable.createContext(shareWith); - offscreenContext.setSynchronized(true); - } - updater = new Updater(); - shouldInitialize = false; - isInitialized = true; + // The backend might set itself to null, indicating it punted to + // a different implementation -- try again + } while (backend == null); } private void handleReshape() { - readBackWidthInPixels = 0; - readBackHeightInPixels = 0; - panelWidth = reshapeWidth; panelHeight = reshapeHeight; @@ -995,76 +465,12 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { } sendReshape = true; - - if (!oglPipelineEnabled) { - if (!hardwareAccelerationDisabled) { - // Use factor larger than 2 during shrinks for some hysteresis - float shrinkFactor = 2.5f; - if ((panelWidth > pbufferWidth ) || (panelHeight > pbufferHeight) || - (panelWidth < (pbufferWidth / shrinkFactor)) || (panelHeight < (pbufferHeight / shrinkFactor))) { - if (DEBUG) { - System.err.println("Resizing pbuffer from (" + pbufferWidth + ", " + pbufferHeight + ") " + - " to fit (" + panelWidth + ", " + panelHeight + ")"); - } - // Must destroy and recreate pbuffer to fit - if (pbuffer != null) { - // Watch for errors during pbuffer destruction (due to - // buggy / bad OpenGL drivers, in particular SiS) and fall - // back to software rendering - try { - pbuffer.destroy(); - } catch (GLException e) { - hardwareAccelerationDisabled = true; - if (DEBUG) { - System.err.println("WARNING: falling back to software rendering due to bugs in OpenGL drivers"); - e.printStackTrace(); - } - } - } - pbuffer = null; - isInitialized = false; - pbufferWidth = getNextPowerOf2(panelWidth); - pbufferHeight = getNextPowerOf2(panelHeight); - if (DEBUG && !hardwareAccelerationDisabled) { - System.err.println("New pbuffer size is (" + pbufferWidth + ", " + pbufferHeight + ")"); - } - initialize(); - } - - // 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... - if (!hardwareAccelerationDisabled) { - readBackWidthInPixels = pbufferWidth; - readBackHeightInPixels = panelHeight; - } else { - // Just disabled hardware acceleration during this resize operation; do a fixup - readBackWidthInPixels = Math.max(1, panelWidth); - readBackHeightInPixels = Math.max(1, panelHeight); - } - } else { - offscreenContext.destroy(); - offscreenDrawable.setSize(Math.max(1, panelWidth), Math.max(1, panelHeight)); - readBackWidthInPixels = Math.max(1, panelWidth); - readBackHeightInPixels = Math.max(1, panelHeight); - } - - if (offscreenImage != null) { - offscreenImage.flush(); - offscreenImage = null; - } - } - + backend.handleReshape(); handleReshape = false; } - // FIXME: it isn't clear whether this works any more given that - // we're accessing the GLDrawable inside of the GLPbuffer directly - // up in reshape() -- need to rethink and clean this up + // This is used as the GLEventListener for the pbuffer-based backend + // as well as the callback mechanism for the other backends class Updater implements GLEventListener { private Graphics g; @@ -1073,24 +479,17 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { } public void init(GLAutoDrawable drawable) { - if (oglPipelineEnabled) { - if (!preGL(g)) { - return; - } + if (!backend.preGL(g)) { + return; } drawableHelper.init(GLJPanel.this); - if (oglPipelineEnabled) { - postGL(g); - } + backend.postGL(g, false); } public void display(GLAutoDrawable drawable) { - if (oglPipelineEnabled) { - if (!preGL(g)) { - return; - } + if (!backend.preGL(g)) { + return; } - if (sendReshape) { if (DEBUG) { System.err.println("glViewport(" + viewportX + ", " + viewportY + ", " + panelWidth + ", " + panelHeight + ")"); @@ -1101,8 +500,148 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { } drawableHelper.display(GLJPanel.this); + backend.postGL(g, true); + } + + public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { + // This is handled above and dispatched directly to the appropriate context + } + + public void displayChanged(GLAutoDrawable 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(); - if (!oglPipelineEnabled) { + class PaintImmediatelyAction implements Runnable { + public void run() { + paintImmediately(0, 0, getWidth(), getHeight()); + } + } + private PaintImmediatelyAction paintImmediatelyAction = new PaintImmediatelyAction(); + + private int getNextPowerOf2(int number) { + // Workaround for problems where 0 width or height are transiently + // seen during layout + if (number == 0) { + return 2; + } + + 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); + } + + private int getGLInteger(GL gl, int which) { + int[] tmp = new int[1]; + gl.glGetIntegerv(which, tmp, 0); + return tmp[0]; + } + + //---------------------------------------------------------------------- + // Implementations of the various backends + // + + // Abstraction of the different rendering backends: i.e., pure + // software / pixmap rendering, pbuffer-based acceleration, Java 2D + // / JOGL bridge + static interface Backend { + // Called each time the backend needs to initialize itself + public void initialize(); + + // Called when the backend should clean up its resources + public void destroy(); + + // Called when the opacity of the GLJPanel is changed + public void setOpaque(boolean opaque); + + // Called to manually create an additional OpenGL context against + // this GLJPanel + public GLContext createContext(GLContext shareWith); + + // Called to get the current backend's GLContext + public GLContext getContext(); + + // Called to fetch the "real" chosen GLCapabilities for the backend + public GLCapabilities getChosenGLCapabilities(); + + // Called to handle a reshape event. When this is called, the + // OpenGL context associated with the backend is not current, to + // make it easier to destroy and re-create pbuffers if necessary. + public void handleReshape(); + + // Called before the OpenGL work is done in init() and display(). + // If false is returned, this render is aborted. + public boolean preGL(Graphics g); + + // Called after the OpenGL work is done in init() and display(). + // The isDisplay argument indicates whether this was called on + // behalf of a call to display() rather than init(). + public void postGL(Graphics g, boolean isDisplay); + + // Called from within paintComponent() to initiate the render + public void doPaintComponent(Graphics g); + } + + // Base class used by both the software (pixmap) and pbuffer + // backends, both of which rely on reading back the OpenGL frame + // buffer and drawing it with a BufferedImage + abstract class AbstractReadbackBackend implements Backend { + // This image is exactly the correct size to render into the panel + protected BufferedImage offscreenImage; + // One of these is used to store the read back pixels before storing + // in the BufferedImage + protected ByteBuffer readBackBytes; + protected IntBuffer readBackInts; + protected int readBackWidthInPixels; + protected int readBackHeightInPixels; + + private int awtFormat; + private int glFormat; + private int glType; + + // For saving/restoring of OpenGL state during ReadPixels + private int[] swapbytes = new int[1]; + private int[] rowlength = new int[1]; + private int[] skiprows = new int[1]; + private int[] skippixels = new int[1]; + private int[] alignment = new int[1]; + + public void setOpaque(boolean opaque) { + if (opaque != isOpaque()) { + if (offscreenImage != null) { + offscreenImage.flush(); + offscreenImage = null; + } + } + } + + public boolean preGL(Graphics g) { + // Empty in this implementation + return true; + } + + public void postGL(Graphics g, boolean isDisplay) { + if (isDisplay) { // Must now copy pixels from offscreen context into surface if (offscreenImage == null) { if (panelWidth > 0 && panelHeight > 0) { @@ -1113,11 +652,6 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { // bottleneck int awtFormat = 0; - int hwGLFormat = 0; - if (!hardwareAccelerationDisabled) { - // This seems to be a good choice on all platforms - hwGLFormat = GL2.GL_UNSIGNED_INT_8_8_8_8_REV; - } // Should be more flexible in these BufferedImage formats; // perhaps see what the preferred image types are on the @@ -1141,9 +675,7 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { case BufferedImage.TYPE_INT_RGB: case BufferedImage.TYPE_INT_ARGB: glFormat = GL2.GL_BGRA; - glType = (hardwareAccelerationDisabled - ? offscreenContext.getOffscreenContextPixelDataType() - : hwGLFormat); + glType = getGLPixelType(); readBackInts = IntBuffer.allocate(readBackWidthInPixels * readBackHeightInPixels); break; @@ -1166,7 +698,7 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { gl.glGetIntegerv(GL2.GL_PACK_SKIP_PIXELS, skippixels, 0); gl.glGetIntegerv(GL2.GL_PACK_ALIGNMENT, alignment, 0); - gl.glPixelStorei(GL2.GL_PACK_SWAP_BYTES, GL2.GL_FALSE); + gl.glPixelStorei(GL2.GL_PACK_SWAP_BYTES, GL.GL_FALSE); gl.glPixelStorei(GL2.GL_PACK_ROW_LENGTH, readBackWidthInPixels); gl.glPixelStorei(GL2.GL_PACK_SKIP_ROWS, 0); gl.glPixelStorei(GL2.GL_PACK_SKIP_PIXELS, 0); @@ -1210,8 +742,7 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { destIncr = offscreenImage.getWidth(); } - if (!hardwareAccelerationDisabled || - offscreenContext.offscreenImageNeedsVerticalFlip()) { + if (flipVertically()) { int srcPos = 0; int destPos = (offscreenImage.getHeight() - 1) * destIncr; for (; destPos >= 0; srcPos += srcIncr, destPos -= destIncr) { @@ -1225,80 +756,664 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable { } } - // Note: image will be drawn back in paintComponent() for - // correctness on all platforms + // Note: image will be drawn back in paintComponent() for + // correctness on all platforms } } - } else { - // Cause OpenGL pipeline to flush its results because - // otherwise it's possible we will buffer up multiple frames' - // rendering results, resulting in apparent mouse lag - GL2 gl = getGL().getGL2(); - gl.glFinish(); + } + } - postGL(g); + public void doPaintComponent(Graphics g) { + doPaintComponentImpl(); + if (offscreenImage != null) { + // Draw resulting image in one shot + g.drawImage(offscreenImage, 0, 0, + offscreenImage.getWidth(), + offscreenImage.getHeight(), + GLJPanel.this); } } - public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { - // This is handled above and dispatched directly to the appropriate context + protected abstract void doPaintComponentImpl(); + protected abstract int getGLPixelType(); + protected abstract boolean flipVertically(); + } + + class SoftwareBackend extends AbstractReadbackBackend { + // Implementation using software rendering + private GLDrawableImpl offscreenDrawable; + private GLContextImpl offscreenContext; + + public void initialize() { + // Fall-through path: create an offscreen context instead + offscreenDrawable = factory.createOffscreenDrawable(offscreenCaps, chooser); + offscreenDrawable.setSize(Math.max(1, panelWidth), Math.max(1, panelHeight)); + offscreenContext = (GLContextImpl) offscreenDrawable.createContext(shareWith); + offscreenContext.setSynchronized(true); + isInitialized = true; } - public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) { + public void destroy() { + if (offscreenContext != null) { + offscreenContext.destroy(); + offscreenContext = null; + } + if (offscreenDrawable != null) { + offscreenDrawable.destroy(); + offscreenDrawable = null; + } } - } - class InitAction implements Runnable { - public void run() { - updater.init(GLJPanel.this); + public GLContext createContext(GLContext shareWith) { + return offscreenDrawable.createContext(shareWith); } - } - private InitAction initAction = new InitAction(); - class DisplayAction implements Runnable { - public void run() { - updater.display(GLJPanel.this); + public GLContext getContext() { + return offscreenContext; } - } - private DisplayAction displayAction = new DisplayAction(); - // This one is used exclusively in the non-hardware-accelerated case - class SwapBuffersAction implements Runnable { - public void run() { - offscreenDrawable.swapBuffers(); + public GLCapabilities getChosenGLCapabilities() { + if (offscreenDrawable == null) { + return null; + } + return offscreenDrawable.getChosenGLCapabilities(); } - } - private SwapBuffersAction swapBuffersAction = new SwapBuffersAction(); - class PaintImmediatelyAction implements Runnable { - public void run() { - paintImmediately(0, 0, getWidth(), getHeight()); + public void handleReshape() { + offscreenContext.destroy(); + offscreenDrawable.setSize(Math.max(1, panelWidth), Math.max(1, panelHeight)); + readBackWidthInPixels = Math.max(1, panelWidth); + readBackHeightInPixels = Math.max(1, panelHeight); + + if (offscreenImage != null) { + offscreenImage.flush(); + offscreenImage = null; + } + } + + protected void doPaintComponentImpl() { + drawableHelper.invokeGL(offscreenDrawable, offscreenContext, displayAction, initAction); + } + + protected int getGLPixelType() { + return offscreenContext.getOffscreenContextPixelDataType(); + } + + protected boolean flipVertically() { + return offscreenContext.offscreenImageNeedsVerticalFlip(); } } - private PaintImmediatelyAction paintImmediatelyAction = new PaintImmediatelyAction(); - private int getNextPowerOf2(int number) { - // Workaround for problems where 0 width or height are transiently - // seen during layout - if (number == 0) { - return 2; + class PbufferBackend extends AbstractReadbackBackend { + private GLPbuffer pbuffer; + private int pbufferWidth = 256; + private int pbufferHeight = 256; + + public void initialize() { + if (pbuffer != null) { + throw new InternalError("Creating pbuffer twice without destroying it (memory leak / correctness bug)"); + } + try { + pbuffer = factory.createGLPbuffer(offscreenCaps, + null, + pbufferWidth, + pbufferHeight, + shareWith); + pbuffer.addGLEventListener(updater); + isInitialized = true; + } catch (GLException e) { + if (DEBUG) { + e.printStackTrace(); + System.err.println("GLJPanel: Falling back on software rendering because of problems creating pbuffer"); + } + hardwareAccelerationDisabled = true; + backend = null; + isInitialized = false; + createAndInitializeBackend(); + } } - if (((number-1) & number) == 0) { - //ex: 8 -> 0b1000; 8-1=7 -> 0b0111; 0b1000&0b0111 == 0 - return number; + public void destroy() { + if (pbuffer != null) { + pbuffer.destroy(); + pbuffer = null; + } } - int power = 0; - while (number > 0) { - number = number>>1; - power++; + + public GLContext createContext(GLContext shareWith) { + return pbuffer.createContext(shareWith); + } + + public GLContext getContext() { + // Workaround for crashes in NetBeans GUI builder + if (pbuffer == null && Beans.isDesignTime()) { + return null; + } + return pbuffer.getContext(); + } + + public GLCapabilities getChosenGLCapabilities() { + if (pbuffer == null) { + return null; + } + return pbuffer.getChosenGLCapabilities(); + } + + public void handleReshape() { + // Use factor larger than 2 during shrinks for some hysteresis + float shrinkFactor = 2.5f; + if ((panelWidth > pbufferWidth) || (panelHeight > pbufferHeight) || + (panelWidth < (pbufferWidth / shrinkFactor)) || (panelHeight < (pbufferHeight / shrinkFactor))) { + if (DEBUG) { + System.err.println("Resizing pbuffer from (" + pbufferWidth + ", " + pbufferHeight + ") " + + " to fit (" + panelWidth + ", " + panelHeight + ")"); + } + // Must destroy and recreate pbuffer to fit + if (pbuffer != null) { + // Watch for errors during pbuffer destruction (due to + // buggy / bad OpenGL drivers, in particular SiS) and fall + // back to software rendering + try { + pbuffer.destroy(); + } catch (GLException e) { + hardwareAccelerationDisabled = true; + backend = null; + isInitialized = false; + // Just disabled hardware acceleration during this resize operation; do a fixup + readBackWidthInPixels = Math.max(1, panelWidth); + readBackHeightInPixels = Math.max(1, panelHeight); + if (DEBUG) { + System.err.println("WARNING: falling back to software rendering due to bugs in OpenGL drivers"); + e.printStackTrace(); + } + createAndInitializeBackend(); + return; + } + } + pbuffer = null; + isInitialized = false; + pbufferWidth = getNextPowerOf2(panelWidth); + pbufferHeight = getNextPowerOf2(panelHeight); + if (DEBUG && !hardwareAccelerationDisabled) { + System.err.println("New pbuffer size is (" + pbufferWidth + ", " + pbufferHeight + ")"); + } + initialize(); + } + + // 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... + readBackWidthInPixels = pbufferWidth; + readBackHeightInPixels = panelHeight; + + if (offscreenImage != null) { + offscreenImage.flush(); + offscreenImage = null; + } + } + + protected void doPaintComponentImpl() { + pbuffer.display(); + } + + protected int getGLPixelType() { + // This seems to be a good choice on all platforms + return GL2.GL_UNSIGNED_INT_8_8_8_8_REV; + } + + protected boolean flipVertically() { + return true; } - return (1<<power); } - private int getGLInteger(GL gl, int which) { - int[] tmp = new int[1]; - gl.glGetIntegerv(which, tmp, 0); - return tmp[0]; + class J2DOGLBackend implements Backend { + // Opaque Object identifier representing the Java2D surface we are + // drawing to; used to determine when to destroy and recreate JOGL + // context + private Object j2dSurface; + // Graphics object being used during Java2D update action + // (absolutely essential to cache this) + private Graphics cached2DGraphics; + // No-op context representing the Java2D OpenGL context + private GLContext j2dContext; + // Context associated with no-op drawable representing the JOGL + // OpenGL context + private GLDrawable joglDrawable; + // The real OpenGL context JOGL uses to render + private GLContext joglContext; + // State captured from Java2D OpenGL context necessary in order to + // properly render into Java2D back buffer + private int[] drawBuffer = new int[1]; + private int[] readBuffer = new int[1]; + // This is required when the FBO option of the Java2D / OpenGL + // pipeline is active + private int[] frameBuffer = new int[1]; + // Current (as of this writing) NVidia drivers have a couple of bugs + // relating to the sharing of framebuffer and renderbuffer objects + // between contexts. It appears we have to (a) reattach the color + // attachment and (b) actually create new depth buffer storage and + // attach it in order for the FBO to behave properly in our context. + private boolean checkedForFBObjectWorkarounds; + private boolean fbObjectWorkarounds; + private int[] frameBufferDepthBuffer; + private int[] frameBufferTexture; + private boolean createNewDepthBuffer; + // Current (as of this writing) ATI drivers have problems when the + // same FBO is bound in two different contexts. Here we check for + // this case and explicitly release the FBO from Java2D's context + // before switching to ours. Java2D will re-bind the FBO when it + // makes its context current the next time. Interestingly, if we run + // this code path on NVidia hardware, it breaks the rendering + // results -- no output is generated. This doesn't appear to be an + // interaction with the abovementioned NVidia-specific workarounds, + // as even if we disable that code the FBO is still reported as + // incomplete in our context. + private boolean checkedGLVendor; + private boolean vendorIsATI; + + // Holding on to this GraphicsConfiguration is a workaround for a + // problem in the Java 2D / JOGL bridge when FBOs are enabled; see + // comment related to Issue 274 below + private GraphicsConfiguration workaroundConfig; + + public void initialize() { + // No-op in this implementation; everything is done lazily + isInitialized = true; + } + + public void destroy() { + Java2D.invokeWithOGLContextCurrent(null, new Runnable() { + public void run() { + if (joglContext != null) { + joglContext.destroy(); + joglContext = null; + } + joglDrawable = null; + if (j2dContext != null) { + j2dContext.destroy(); + j2dContext = null; + } + } + }); + } + + public void setOpaque(boolean opaque) { + // Empty in this implementation + } + + public GLContext createContext(GLContext shareWith) { + // FIXME: should implement this, but it was not properly + // implemented before the refactoring anyway + throw new GLException("Not yet implemented"); + } + + public GLContext getContext() { + return joglContext; + } + + public GLCapabilities getChosenGLCapabilities() { + // FIXME: should do better than this; is it possible to using only platform-independent code? + return new GLCapabilities(); + } + + public void handleReshape() { + // Empty in this implementation + } + + public boolean preGL(Graphics g) { + GL2 gl = joglContext.getGL().getGL2(); + // Set up needed state in JOGL context from Java2D context + gl.glEnable(GL2.GL_SCISSOR_TEST); + Rectangle r = Java2D.getOGLScissorBox(g); + + if (r == null) { + if (DEBUG && VERBOSE) { + System.err.println("Java2D.getOGLScissorBox() returned null"); + } + return false; + } + if (DEBUG && VERBOSE) { + System.err.println("GLJPanel: gl.glScissor(" + r.x + ", " + r.y + ", " + r.width + ", " + r.height + ")"); + } + + gl.glScissor(r.x, r.y, r.width, r.height); + Rectangle oglViewport = Java2D.getOGLViewport(g, panelWidth, panelHeight); + // If the viewport X or Y changes, in addition to the panel's + // width or height, we need to send a reshape operation to the + // client + if ((viewportX != oglViewport.x) || + (viewportY != oglViewport.y)) { + sendReshape = true; + if (DEBUG) { + System.err.println("Sending reshape because viewport changed"); + System.err.println(" viewportX (" + viewportX + ") ?= oglViewport.x (" + oglViewport.x + ")"); + System.err.println(" viewportY (" + viewportY + ") ?= oglViewport.y (" + oglViewport.y + ")"); + } + } + viewportX = oglViewport.x; + viewportY = oglViewport.y; + + // If the FBO option is active, bind to the FBO from the Java2D + // context. + // Note that all of the plumbing in the context sharing stuff will + // allow us to bind to this object since it's in our namespace. + if (Java2D.isFBOEnabled() && + Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) { + if (DEBUG && VERBOSE) { + System.err.println("GLJPanel: Binding to framebuffer object " + frameBuffer[0]); + } + + // The texture target for Java2D's OpenGL pipeline when using FBOs + // -- either GL_TEXTURE_2D or GL_TEXTURE_RECTANGLE_ARB + int fboTextureTarget = Java2D.getOGLTextureType(g); + + if (!checkedForFBObjectWorkarounds) { + checkedForFBObjectWorkarounds = true; + gl.glBindTexture(fboTextureTarget, 0); + gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, frameBuffer[0]); + if (gl.glCheckFramebufferStatus(GL2.GL_FRAMEBUFFER) != + GL2.GL_FRAMEBUFFER_COMPLETE) { + // Need to do workarounds + fbObjectWorkarounds = true; + createNewDepthBuffer = true; + if (DEBUG) { + System.err.println("-- GLJPanel: discovered frame_buffer_object workarounds to be necessary"); + } + } else { + // Don't need the frameBufferTexture temporary any more + frameBufferTexture = null; + } + } + + if (fbObjectWorkarounds && createNewDepthBuffer) { + if (frameBufferDepthBuffer == null) + frameBufferDepthBuffer = new int[1]; + + // Create our own depth renderbuffer and associated storage + // If we have an old one, delete it + if (frameBufferDepthBuffer[0] != 0) { + gl.glDeleteRenderbuffers(1, frameBufferDepthBuffer, 0); + frameBufferDepthBuffer[0] = 0; + } + + gl.glBindTexture(fboTextureTarget, frameBufferTexture[0]); + int[] width = new int[1]; + int[] height = new int[1]; + gl.glGetTexLevelParameteriv(fboTextureTarget, 0, GL2.GL_TEXTURE_WIDTH, width, 0); + gl.glGetTexLevelParameteriv(fboTextureTarget, 0, GL2.GL_TEXTURE_HEIGHT, height, 0); + + gl.glGenRenderbuffers(1, frameBufferDepthBuffer, 0); + if (DEBUG) { + System.err.println("GLJPanel: Generated frameBufferDepthBuffer " + frameBufferDepthBuffer[0] + + " with width " + width[0] + ", height " + height[0]); + } + + gl.glBindRenderbuffer(GL2.GL_RENDERBUFFER, frameBufferDepthBuffer[0]); + // FIXME: may need a loop here like in Java2D + gl.glRenderbufferStorage(GL2.GL_RENDERBUFFER, GL2.GL_DEPTH_COMPONENT24, width[0], height[0]); + + gl.glBindRenderbuffer(GL2.GL_RENDERBUFFER, 0); + createNewDepthBuffer = false; + } + + gl.glBindTexture(fboTextureTarget, 0); + gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, frameBuffer[0]); + + if (fbObjectWorkarounds) { + // Hook up the color and depth buffer attachment points for this framebuffer + gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, + GL2.GL_COLOR_ATTACHMENT0, + fboTextureTarget, + frameBufferTexture[0], + 0); + if (DEBUG && VERBOSE) { + System.err.println("GLJPanel: frameBufferDepthBuffer: " + frameBufferDepthBuffer[0]); + } + gl.glFramebufferRenderbuffer(GL2.GL_FRAMEBUFFER, + GL2.GL_DEPTH_ATTACHMENT, + GL2.GL_RENDERBUFFER, + frameBufferDepthBuffer[0]); + } + + if (DEBUG) { + int status = gl.glCheckFramebufferStatus(GL2.GL_FRAMEBUFFER); + if (status != GL2.GL_FRAMEBUFFER_COMPLETE) { + throw new GLException("Error: framebuffer was incomplete: status = 0x" + + Integer.toHexString(status)); + } + } + } else { + if (DEBUG && VERBOSE) { + System.err.println("GLJPanel: Setting up drawBuffer " + drawBuffer[0] + + " and readBuffer " + readBuffer[0]); + } + + gl.glDrawBuffer(drawBuffer[0]); + gl.glReadBuffer(readBuffer[0]); + } + + return true; + } + + public void postGL(Graphics g, boolean isDisplay) { + // Cause OpenGL pipeline to flush its results because + // otherwise it's possible we will buffer up multiple frames' + // rendering results, resulting in apparent mouse lag + GL gl = getGL(); + gl.glFinish(); + + if (Java2D.isFBOEnabled() && + Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) { + // Unbind the framebuffer from our context to work around + // apparent driver bugs or at least unspecified behavior causing + // OpenGL to run out of memory with certain cards and drivers + gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0); + } + } + + public void doPaintComponent(final Graphics g) { + // This is a workaround for an issue in the Java 2D / JOGL + // bridge (reported by an end user as JOGL Issue 274) where Java + // 2D can occasionally leave its internal OpenGL context current + // to the on-screen window rather than its internal "scratch" + // pbuffer surface to which the FBO is attached. JOGL expects to + // find a stable OpenGL drawable (on Windows, an HDC) upon which + // it can create another OpenGL context. It turns out that, on + // Windows, when Java 2D makes its internal OpenGL context + // current against the window in order to put pixels on the + // screen, it gets the device context for the window, makes its + // context current, and releases the device context. This means + // that when JOGL's Runnable gets to run below, the HDC is + // already invalid. The workaround for this is to force Java 2D + // to make its context current to the scratch surface, which we + // can do by executing an empty Runnable with the "shared" + // context current. This will be fixed in a Java SE 6 update + // release, hopefully 6u2. + if (Java2D.isFBOEnabled()) { + if (workaroundConfig == null) { + workaroundConfig = GraphicsEnvironment. + getLocalGraphicsEnvironment(). + getDefaultScreenDevice(). + getDefaultConfiguration(); + } + Java2D.invokeWithOGLSharedContextCurrent(workaroundConfig, new Runnable() { public void run() {}}); + } + + Java2D.invokeWithOGLContextCurrent(g, new Runnable() { + public void run() { + if (DEBUG && VERBOSE) { + System.err.println("-- In invokeWithOGLContextCurrent"); + } + + // Create no-op context representing Java2D context + if (j2dContext == null) { + j2dContext = factory.createExternalGLContext(); + if (DEBUG) { + j2dContext.setGL(new DebugGL2(j2dContext.getGL().getGL2())); + } + + // Check to see whether we can support the requested + // capabilities or need to fall back to a pbuffer + // FIXME: add more checks? + + j2dContext.makeCurrent(); + GL gl = j2dContext.getGL(); + if ((getGLInteger(gl, GL2.GL_RED_BITS) < offscreenCaps.getRedBits()) || + (getGLInteger(gl, GL2.GL_GREEN_BITS) < offscreenCaps.getGreenBits()) || + (getGLInteger(gl, GL2.GL_BLUE_BITS) < offscreenCaps.getBlueBits()) || + // (getGLInteger(gl, GL2.GL_ALPHA_BITS) < offscreenCaps.getAlphaBits()) || + (getGLInteger(gl, GL2.GL_ACCUM_RED_BITS) < offscreenCaps.getAccumRedBits()) || + (getGLInteger(gl, GL2.GL_ACCUM_GREEN_BITS) < offscreenCaps.getAccumGreenBits()) || + (getGLInteger(gl, GL2.GL_ACCUM_BLUE_BITS) < offscreenCaps.getAccumBlueBits()) || + (getGLInteger(gl, GL2.GL_ACCUM_ALPHA_BITS) < offscreenCaps.getAccumAlphaBits()) || + // (getGLInteger(gl, GL2.GL_DEPTH_BITS) < offscreenCaps.getDepthBits()) || + (getGLInteger(gl, GL2.GL_STENCIL_BITS) < offscreenCaps.getStencilBits())) { + if (DEBUG) { + System.err.println("GLJPanel: Falling back to pbuffer-based support because Java2D context insufficient"); + System.err.println(" Available Required"); + System.err.println("GL_RED_BITS " + getGLInteger(gl, GL2.GL_RED_BITS) + " " + offscreenCaps.getRedBits()); + System.err.println("GL_GREEN_BITS " + getGLInteger(gl, GL2.GL_GREEN_BITS) + " " + offscreenCaps.getGreenBits()); + System.err.println("GL_BLUE_BITS " + getGLInteger(gl, GL2.GL_BLUE_BITS) + " " + offscreenCaps.getBlueBits()); + System.err.println("GL_ALPHA_BITS " + getGLInteger(gl, GL2.GL_ALPHA_BITS) + " " + offscreenCaps.getAlphaBits()); + System.err.println("GL_ACCUM_RED_BITS " + getGLInteger(gl, GL2.GL_ACCUM_RED_BITS) + " " + offscreenCaps.getAccumRedBits()); + System.err.println("GL_ACCUM_GREEN_BITS " + getGLInteger(gl, GL2.GL_ACCUM_GREEN_BITS) + " " + offscreenCaps.getAccumGreenBits()); + System.err.println("GL_ACCUM_BLUE_BITS " + getGLInteger(gl, GL2.GL_ACCUM_BLUE_BITS) + " " + offscreenCaps.getAccumBlueBits()); + System.err.println("GL_ACCUM_ALPHA_BITS " + getGLInteger(gl, GL2.GL_ACCUM_ALPHA_BITS) + " " + offscreenCaps.getAccumAlphaBits()); + System.err.println("GL_DEPTH_BITS " + getGLInteger(gl, GL2.GL_DEPTH_BITS) + " " + offscreenCaps.getDepthBits()); + System.err.println("GL_STENCIL_BITS " + getGLInteger(gl, GL2.GL_STENCIL_BITS) + " " + offscreenCaps.getStencilBits()); + } + isInitialized = false; + backend = null; + oglPipelineEnabled = false; + handleReshape = true; + j2dContext.release(); + j2dContext.destroy(); + j2dContext = null; + return; + } + j2dContext.release(); + } + + j2dContext.makeCurrent(); + try { + captureJ2DState(j2dContext.getGL(), g); + Object curSurface = Java2D.getOGLSurfaceIdentifier(g); + if (curSurface != null) { + if (j2dSurface != curSurface) { + if (joglContext != null) { + joglContext.destroy(); + joglContext = null; + joglDrawable = null; + sendReshape = true; + if (DEBUG) { + System.err.println("Sending reshape because surface changed"); + System.err.println("New surface = " + curSurface); + } + } + j2dSurface = curSurface; + } + if (joglContext == null) { + if (factory.canCreateExternalGLDrawable()) { + joglDrawable = factory.createExternalGLDrawable(); + joglContext = joglDrawable.createContext(shareWith); + } else if (((GLDrawableFactoryImpl) factory).canCreateContextOnJava2DSurface()) { + // Mac OS X code path + joglContext = ((GLDrawableFactoryImpl) factory).createContextOnJava2DSurface(g, shareWith); + } + if (DEBUG) { + joglContext.setGL(new DebugGL2(joglContext.getGL().getGL2())); + } + + if (Java2D.isFBOEnabled() && + Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT && + fbObjectWorkarounds) { + createNewDepthBuffer = true; + } + } + if (joglContext instanceof Java2DGLContext) { + // Mac OS X code path + ((Java2DGLContext) joglContext).setGraphics(g); + } + + if (DEBUG && VERBOSE && Java2D.isFBOEnabled()) { + System.err.print("-- Surface type: "); + int surfaceType = Java2D.getOGLSurfaceType(g); + if (surfaceType == Java2D.UNDEFINED) { + System.err.println("UNDEFINED"); + } else if (surfaceType == Java2D.WINDOW) { + System.err.println("WINDOW"); + } else if (surfaceType == Java2D.PBUFFER) { + System.err.println("PBUFFER"); + } else if (surfaceType == Java2D.TEXTURE) { + System.err.println("TEXTURE"); + } else if (surfaceType == Java2D.FLIP_BACKBUFFER) { + System.err.println("FLIP_BACKBUFFER"); + } else if (surfaceType == Java2D.FBOBJECT) { + System.err.println("FBOBJECT"); + } else { + System.err.println("(Unknown surface type " + surfaceType + ")"); + } + } + + drawableHelper.invokeGL(joglDrawable, joglContext, displayAction, initAction); + } + } finally { + j2dContext.release(); + } + } + }); + } + + private void captureJ2DState(GL gl, Graphics g) { + gl.glGetIntegerv(GL2.GL_DRAW_BUFFER, drawBuffer, 0); + gl.glGetIntegerv(GL2.GL_READ_BUFFER, readBuffer, 0); + if (Java2D.isFBOEnabled() && + Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) { + if (DEBUG && VERBOSE) { + System.err.println("GLJPanel: Fetching GL_FRAMEBUFFER_BINDING_EXT"); + } + gl.glGetIntegerv(GL2.GL_FRAMEBUFFER_BINDING, frameBuffer, 0); + + if (fbObjectWorkarounds || + !checkedForFBObjectWorkarounds) { + // See above for description of what we are doing here + if (frameBufferTexture == null) + frameBufferTexture = new int[1]; + + // Query the framebuffer for its color buffer so we can hook + // it back up in our context (should not be necessary) + gl.glGetFramebufferAttachmentParameteriv(GL2.GL_FRAMEBUFFER, + GL2.GL_COLOR_ATTACHMENT0, + GL2.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, + frameBufferTexture, 0); + if (DEBUG && VERBOSE) { + System.err.println("GLJPanel: FBO COLOR_ATTACHMENT0: " + frameBufferTexture[0]); + } + } + + if (!checkedGLVendor) { + checkedGLVendor = true; + String vendor = gl.glGetString(GL2.GL_VENDOR); + + if ((vendor != null) && + vendor.startsWith("ATI")) { + vendorIsATI = true; + } + } + + if (vendorIsATI) { + // Unbind the FBO from Java2D's context as it appears that + // driver bugs on ATI's side are causing problems if the FBO is + // simultaneously bound to more than one context. Java2D will + // re-bind the FBO during the next validation of its context. + // Note: this breaks rendering at least on NVidia hardware + gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, 0); + } + } + } } } |