/* * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistribution of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistribution in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN * MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed or intended for use * in the design, construction, operation or maintenance of any nuclear * facility. * * Sun gratefully acknowledges that this software was originally authored * and developed by Kenneth Bradley Russell and Christopher John Kline. */ package net.java.games.jogl; import java.awt.Component; 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 javax.swing.JComponent; import javax.swing.JPanel; import net.java.games.jogl.impl.*; // FIXME: Subclasses need to call resetGLFunctionAvailability() on their // context whenever the displayChanged() function is called on their // GLEventListeners /** 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 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; private DataBufferByte dbByte; private DataBufferInt dbInt; private Object semaphore = new Object(); private boolean repaintDone; // 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]; private int[] alignment = new int[1]; GLJPanel(GLCapabilities capabilities, GLCapabilitiesChooser chooser, GLDrawable shareWith) { super(); // Works around problems on many vendors' cards; we don't need a // back buffer for the offscreen surface anyway GLCapabilities myCaps = (GLCapabilities) capabilities.clone(); myCaps.setDoubleBuffered(false); context = GLContextFactory.getFactory().createGLContext(null, myCaps, chooser, GLContextHelper.getContext(shareWith)); } public void display() { // Multithreaded redrawing of Swing components is not allowed try { synchronized(semaphore) { repaintDone = false; repaint(); while (!repaintDone) { semaphore.wait(); } } } catch (InterruptedException e) { } } /** Overridden from JComponent; calls {@link #display}. Should not be invoked by applications directly. */ public void paintComponent(Graphics g) { displayAction.setGraphics(g); context.invokeGL(displayAction, false, initAction); synchronized(semaphore) { repaintDone = true; semaphore.notifyAll(); } } /** Overridden from Canvas; causes {@link GLDrawableHelper#reshape} to be called on all registered {@link GLEventListener}s. Called automatically by the AWT; should not be invoked by applications 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; final int fheight = height; context.resizeOffscreenContext(width, height); context.invokeGL(new Runnable() { public void run() { getGL().glViewport(fx, fy, fwidth, fheight); drawableHelper.reshape(GLJPanel.this, fx, fy, fwidth, fheight); if (offscreenImage != null && (offscreenImage.getWidth() != context.getOffscreenContextWidth() || offscreenImage.getHeight() != context.getOffscreenContextHeight())) { offscreenImage.flush(); offscreenImage = null; } } }, true, initAction); } public void addGLEventListener(GLEventListener listener) { drawableHelper.addGLEventListener(listener); } public void removeGLEventListener(GLEventListener listener) { drawableHelper.removeGLEventListener(listener); } public GL getGL() { return context.getGL(); } public void setGL(GL gl) { context.setGL(gl); } public GLU getGLU() { return context.getGLU(); } public void setGLU(GLU glu) { context.setGLU(glu); } public void setRenderingThread(Thread currentThreadOrNull) throws GLException { // Not supported for GLJPanel because all repaint requests must be // handled by the AWT thread } public Thread getRenderingThread() { return context.getRenderingThread(); } public void setNoAutoRedrawMode(boolean noAutoRedraws) { } public boolean getNoAutoRedrawMode() { return false; } public void setAutoSwapBufferMode(boolean onOrOff) { context.setAutoSwapBufferMode(onOrOff); } public boolean getAutoSwapBufferMode() { return context.getAutoSwapBufferMode(); } public void swapBuffers() { context.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) return false; } public GLPbuffer createOffscreenDrawable(GLCapabilities capabilities, int initialWidth, int initialHeight) { throw new GLException("Not supported"); } GLContext getContext() { return context; } //---------------------------------------------------------------------- // Internals only below this point // class InitAction implements Runnable { public void run() { drawableHelper.init(GLJPanel.this); } } private InitAction initAction = new InitAction(); class DisplayAction implements Runnable { private Graphics g; public void setGraphics(Graphics g) { this.g = g; } public void run() { 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); } } 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()); } // 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]); 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); } } } private DisplayAction displayAction = new DisplayAction(); class SwapBuffersAction implements Runnable { public void run() { context.swapBuffers(); } } private SwapBuffersAction swapBuffersAction = new SwapBuffersAction(); }