/*
* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
* Copyright (c) 2010 JogAmp Community. 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
* MICROSYSTEMS, 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 javax.media.opengl.awt;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.beans.Beans;
import java.nio.IntBuffer;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;
import javax.media.nativewindow.AbstractGraphicsDevice;
import javax.media.nativewindow.NativeSurface;
import javax.media.nativewindow.WindowClosingProtocol;
import javax.media.opengl.DefaultGLCapabilitiesChooser;
import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GL2ES3;
import javax.media.opengl.GL2GL3;
import javax.media.opengl.GLAnimatorControl;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLCapabilitiesChooser;
import javax.media.opengl.GLCapabilitiesImmutable;
import javax.media.opengl.GLContext;
import javax.media.opengl.GLDrawable;
import javax.media.opengl.GLDrawableFactory;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLException;
import javax.media.opengl.GLFBODrawable;
import javax.media.opengl.GLProfile;
import javax.media.opengl.GLRunnable;
import javax.media.opengl.Threading;
import javax.swing.JPanel;
import jogamp.opengl.Debug;
import jogamp.opengl.GLContextImpl;
import jogamp.opengl.GLDrawableFactoryImpl;
import jogamp.opengl.GLDrawableHelper;
import jogamp.opengl.GLDrawableImpl;
import jogamp.opengl.awt.AWTTilePainter;
import jogamp.opengl.awt.Java2D;
import jogamp.opengl.util.glsl.GLSLTextureRaster;
import com.jogamp.common.util.awt.AWTEDTExecutor;
import com.jogamp.nativewindow.awt.AWTWindowClosingProtocol;
import com.jogamp.opengl.FBObject;
import com.jogamp.opengl.util.GLPixelBuffer.GLPixelAttributes;
import com.jogamp.opengl.util.GLPixelBuffer.SingletonGLPixelBufferProvider;
import com.jogamp.opengl.util.GLDrawableUtil;
import com.jogamp.opengl.util.GLPixelStorageModes;
import com.jogamp.opengl.util.TileRenderer;
import com.jogamp.opengl.util.awt.AWTGLPixelBuffer;
import com.jogamp.opengl.util.awt.AWTGLPixelBuffer.AWTGLPixelBufferProvider;
import com.jogamp.opengl.util.awt.AWTGLPixelBuffer.SingleAWTGLPixelBufferProvider;
/** 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.
The GLJPanel can be made transparent by creating it with a
GLCapabilities object with alpha bits specified and calling {@link
#setOpaque}(false). Pixels with resulting OpenGL alpha values less
than 1.0 will be overlaid on any underlying Swing rendering.
This component attempts to use hardware-accelerated rendering via FBO or pbuffers and
falls back on to software rendering if none of the former are available
using {@link GLDrawableFactory#createOffscreenDrawable(AbstractGraphicsDevice, GLCapabilitiesImmutable, GLCapabilitiesChooser, int, int) GLDrawableFactory.createOffscreenDrawable(..)}.
In case FBO is used and GLSL is available, a fragment shader is utilized
to flip the FBO texture vertically. This hardware-accelerated step can be disabled via system property jogl.gljpanel.noglsl.
See details here.
The OpenGL path is concluded by copying the rendered pixels an {@link BufferedImage} via {@link GL#glReadPixels(int, int, int, int, int, int, java.nio.Buffer) glReadPixels(..)}
for later Java2D composition.
In case the above mentioned GLSL vertical-flipping is not performed,
{@link System#arraycopy(Object, int, Object, int, int) System.arraycopy(..)} is used line by line.
This step causes more CPU load per frame and is not hardware-accelerated.
Finally the Java2D compositioning takes place via via {@link Graphics#drawImage(java.awt.Image, int, int, int, int, java.awt.image.ImageObserver) Graphics.drawImage(...)}
on the prepared {@link BufferedImage} as described above.
The FBO / GLSL code path uses one texture-unit and binds the FBO texture to it's active texture-target,
see {@link #setTextureUnit(int)} and {@link #getTextureUnit()}.
If the application uses the same texture-unit, ensure it setup their texture properly, i.e. texture-unit bind, enable and then it's parameters,
see {@link Texture#textureCallOrder Order of Texture Commands}.
*/
@SuppressWarnings("serial")
public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosingProtocol, AWTPrintLifecycle {
private static final boolean DEBUG;
private static final boolean DEBUG_VIEWPORT;
private static final boolean USE_GLSL_TEXTURE_RASTERIZER;
/** Indicates whether the Java 2D OpenGL pipeline is requested by user. */
private static final boolean java2dOGLEnabledByProp;
/** Indicates whether the Java 2D OpenGL pipeline is enabled, resource-compatible and requested by user. */
private static final boolean useJava2DGLPipeline;
/** Indicates whether the Java 2D OpenGL pipeline's usage is error free. */
private static boolean java2DGLPipelineOK;
static {
Debug.initSingleton();
DEBUG = Debug.debug("GLJPanel");
DEBUG_VIEWPORT = Debug.isPropertyDefined("jogl.debug.GLJPanel.Viewport", true);
USE_GLSL_TEXTURE_RASTERIZER = !Debug.isPropertyDefined("jogl.gljpanel.noglsl", true);
boolean enabled = false;
final String sVal = System.getProperty("sun.java2d.opengl");
if( null != sVal ) {
enabled = Boolean.valueOf(sVal);
}
Debug.initSingleton();
java2dOGLEnabledByProp = enabled && !Debug.isPropertyDefined("jogl.gljpanel.noogl", true);
enabled = false;
if( java2dOGLEnabledByProp ) {
// Force eager initialization of part of the Java2D class since
// otherwise it's likely it will try to be initialized while on
// the Queue Flusher Thread, which is not allowed
if (Java2D.isOGLPipelineResourceCompatible() && Java2D.isFBOEnabled()) {
if( null != Java2D.getShareContext(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice()) ) {
enabled = true;
}
}
}
useJava2DGLPipeline = enabled;
java2DGLPipelineOK = enabled;
if( DEBUG ) {
System.err.println("GLJPanel: java2dOGLEnabledByProp "+java2dOGLEnabledByProp);
System.err.println("GLJPanel: useJava2DGLPipeline "+useJava2DGLPipeline);
System.err.println("GLJPanel: java2DGLPipelineOK "+java2DGLPipelineOK);
}
}
private static SingleAWTGLPixelBufferProvider singleAWTGLPixelBufferProvider = null;
private static synchronized SingleAWTGLPixelBufferProvider getSingleAWTGLPixelBufferProvider() {
if( null == singleAWTGLPixelBufferProvider ) {
singleAWTGLPixelBufferProvider = new SingleAWTGLPixelBufferProvider( true /* allowRowStride */ );
}
return singleAWTGLPixelBufferProvider;
}
private GLDrawableHelper helper = new GLDrawableHelper();
private volatile boolean isInitialized;
//
// Data used for either pbuffers or pixmap-based offscreen surfaces
//
private AWTGLPixelBufferProvider customPixelBufferProvider = null;
/** Single buffered offscreen caps */
private GLCapabilitiesImmutable offscreenCaps;
private GLProfile glProfile;
private GLDrawableFactoryImpl factory;
private GLCapabilitiesChooser chooser;
private GLContext shareWith;
private int additionalCtxCreationFlags = 0;
// Lazy reshape notification: reshapeWidth -> panelWidth -> backend.width
private boolean handleReshape = false;
private boolean sendReshape = true;
// For handling reshape events lazily: reshapeWidth -> panelWidth -> backend.width
private int reshapeWidth;
private int reshapeHeight;
// Width of the actual GLJPanel: reshapeWidth -> panelWidth -> backend.width
private int panelWidth = 0;
private int panelHeight = 0;
// These are always set to (0, 0) except when the Java2D / OpenGL
// pipeline is active
private int viewportX;
private int viewportY;
private int requestedTextureUnit = 0; // default
// The backend in use
private volatile Backend backend;
// Used by all backends either directly or indirectly to hook up callbacks
private Updater updater = new Updater();
private boolean oglPipelineUsable() {
return null == customPixelBufferProvider && useJava2DGLPipeline && java2DGLPipelineOK;
}
private AWTWindowClosingProtocol awtWindowClosingProtocol =
new AWTWindowClosingProtocol(this, new Runnable() {
@Override
public void run() {
GLJPanel.this.destroy();
}
}, null);
/** Creates a new GLJPanel component with a default set of OpenGL
capabilities and using the default OpenGL capabilities selection
mechanism.
* @throws GLException if no default profile is available for the default desktop device.
*/
public GLJPanel() throws GLException {
this(null);
}
/** Creates a new GLJPanel component with the requested set of
OpenGL capabilities, using the default OpenGL capabilities
selection mechanism.
* @throws GLException if no GLCapabilities are given and no default profile is available for the default desktop device.
*/
public GLJPanel(GLCapabilitiesImmutable userCapsRequest) throws GLException {
this(userCapsRequest, null, null);
}
/** Creates a new GLJPanel component. The passed GLCapabilities
specifies the OpenGL capabilities for the component; if null, a
default set of capabilities is used. The GLCapabilitiesChooser
specifies the algorithm for selecting one of the available
GLCapabilities for the component; a DefaultGLCapabilitesChooser
is used if null is passed for this argument. The passed
GLContext specifies an OpenGL context with which to share
textures, display lists and other OpenGL state, and may be null
if sharing is not desired. See the note in the overview documentation on
context sharing.
Note: Sharing cannot be enabled using J2D OpenGL FBO sharing,
since J2D GL Context must be shared and we can only share one context.
* @throws GLException if no GLCapabilities are given and no default profile is available for the default desktop device.
*/
public GLJPanel(GLCapabilitiesImmutable userCapsRequest, GLCapabilitiesChooser chooser, GLContext shareWith)
throws GLException
{
super();
// Works around problems on many vendors' cards; we don't need a
// back buffer for the offscreen surface anyway
{
GLCapabilities caps;
if (userCapsRequest != null) {
caps = (GLCapabilities) userCapsRequest.cloneMutable();
} else {
caps = new GLCapabilities(GLProfile.getDefault(GLProfile.getDefaultDevice()));
}
caps.setDoubleBuffered(false);
offscreenCaps = caps;
}
this.glProfile = offscreenCaps.getGLProfile();
this.factory = GLDrawableFactoryImpl.getFactoryImpl(glProfile);
this.chooser = ((chooser != null) ? chooser : new DefaultGLCapabilitiesChooser());
this.shareWith = shareWith;
this.setFocusable(true); // allow keyboard input!
}
public AWTGLPixelBufferProvider getCustomPixelBufferProvider() { return customPixelBufferProvider; }
/**
* @param custom custom {@link AWTGLPixelBufferProvider}
* @throws IllegalArgumentException if custom is null
* @throws IllegalStateException if backend is already realized, i.e. this instanced already painted once.
*/
public void setPixelBufferProvider(AWTGLPixelBufferProvider custom) throws IllegalArgumentException, IllegalStateException {
if( null == custom ) {
throw new IllegalArgumentException("Null PixelBufferProvider");
}
if( null != backend ) {
throw new IllegalStateException("Backend already realized.");
}
customPixelBufferProvider = custom;
}
@Override
public final Object getUpstreamWidget() {
return this;
}
@Override
public void display() {
if( isVisible() ) {
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 {
EventQueue.invokeAndWait(paintImmediatelyAction);
} catch (Exception e) {
throw new GLException(e);
}
}
}
}
protected void dispose() {
if(DEBUG) {
System.err.println(getThreadName()+": GLJPanel.dispose() - start");
// Thread.dumpStack();
}
if (backend != null && backend.getContext() != null) {
boolean animatorPaused = false;
GLAnimatorControl animator = getAnimator();
if(null!=animator) {
animatorPaused = animator.pause();
}
if(backend.getContext().isCreated()) {
Threading.invoke(true, disposeAction, getTreeLock());
}
if(null != backend) {
// not yet destroyed due to backend.isUsingOwnThreadManagment() == true
backend.destroy();
isInitialized = false;
}
if(animatorPaused) {
animator.resume();
}
}
if(DEBUG) {
System.err.println(getThreadName()+": GLJPanel.dispose() - stop");
}
}
/**
* Just an alias for removeNotify
*/
@Override
public void destroy() {
removeNotify();
}
/** 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
to function properly.
paintComponent in class javax.swing.JComponent
*/
@Override
protected void paintComponent(final Graphics g) {
if (Beans.isDesignTime()) {
// Make GLJPanel behave better in NetBeans GUI builder
g.setColor(Color.BLACK);
g.fillRect(0, 0, getWidth(), getHeight());
FontMetrics fm = g.getFontMetrics();
String name = getName();
if (name == null) {
name = getClass().getName();
int idx = name.lastIndexOf('.');
if (idx >= 0) {
name = name.substring(idx + 1);
}
}
Rectangle2D bounds = fm.getStringBounds(name, g);
g.setColor(Color.WHITE);
g.drawString(name,
(int) ((getWidth() - bounds.getWidth()) / 2),
(int) ((getHeight() + bounds.getHeight()) / 2));
return;
}
if (backend == null || !isInitialized) {
createAndInitializeBackend();
}
if (!isInitialized) {
return;
}
// NOTE: must do this when the context is not current as it may
// involve destroying the pbuffer (current context) and
// re-creating it -- tricky to do properly while the context is
// current
if (handleReshape) {
handleReshape = false;
sendReshape = handleReshape();
}
if( isVisible() && !printActive ) {
updater.setGraphics(g);
backend.doPaintComponent(g);
}
}
/** Overridden to track when this component is added to a container.
Subclasses which override this method must call
super.addNotify() in their addNotify() method in order to
function properly.
addNotify in class java.awt.Component
*/
@Override
public void addNotify() {
super.addNotify();
awtWindowClosingProtocol.addClosingListener();
if (DEBUG) {
System.err.println(getThreadName()+": GLJPanel.addNotify()");
}
}
/** Overridden to track when this component is removed from a
container. Subclasses which override this method must call
super.removeNotify() in their removeNotify() method in order to
function properly.
removeNotify in class java.awt.Component
*/
@Override
public void removeNotify() {
awtWindowClosingProtocol.removeClosingListener();
dispose();
super.removeNotify();
}
/** Overridden to cause {@link GLDrawableHelper#reshape} to be
called on all registered {@link GLEventListener}s. Subclasses
which override this method must call super.reshape() in
their reshape() method in order to function properly.
*
* {@inheritDoc}
*/
@SuppressWarnings("deprecation")
@Override
public void reshape(int x, int y, int width, int height) {
super.reshape(x, y, width, height);
if (DEBUG) {
System.err.println(getThreadName()+": GLJPanel.reshape: " +reshapeWidth+"x"+reshapeHeight + " -> " + width+"x"+height);
}
if( !printActive ) {
// reshapeX = x;
// reshapeY = y;
reshapeWidth = width;
reshapeHeight = height;
handleReshape = true;
}
}
private static final int PRINT_TILE_SIZE = 512;
private volatile boolean printActive = false;
private boolean printUseAA = false;
private GLAnimatorControl printAnimator = null;
private GLAutoDrawable printGLAD = null;
private AWTTilePainter printAWTTiles = null;
@Override
public void setupPrint(Graphics2D g2d, double scaleMatX, double scaleMatY) {
if (!isInitialized) {
if(DEBUG) {
System.err.println(getThreadName()+": Info: GLJPanel setupPrint - skipped GL render, drawable not valid yet");
}
return; // not yet available ..
}
if( !isVisible() ) {
if(DEBUG) {
System.err.println(getThreadName()+": Info: GLJPanel setupPrint - skipped GL render, drawable visible");
}
return; // not yet available ..
}
printActive = true;
sendReshape = false; // clear reshape flag
handleReshape = false; // ditto
final RenderingHints rHints = g2d.getRenderingHints();
{
final Object _useAA = rHints.get(RenderingHints.KEY_ANTIALIASING);
printUseAA = null != _useAA && ( _useAA == RenderingHints.VALUE_ANTIALIAS_DEFAULT || _useAA == RenderingHints.VALUE_ANTIALIAS_ON );
}
if( DEBUG ) {
System.err.println("AWT print.setup: canvasSize "+getWidth()+"x"+getWidth()+", scaleMat "+scaleMatX+" x "+scaleMatY+", useAA "+printUseAA+", printAnimator "+printAnimator);
{
final Set> rEntries = rHints.entrySet();
int count = 0;
for(Iterator> rEntryIter = rEntries.iterator(); rEntryIter.hasNext(); count++) {
final Entry