![JAR search and dependency download from the Maven repository](/logo.png)
com.metsci.glimpse.layers.GlimpseCanvasView Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2020, Metron, 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:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions 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 Metron, Inc. nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL METRON, INC. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.metsci.glimpse.layers;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Objects.equal;
import static com.jogamp.opengl.GLContext.CONTEXT_NOT_CURRENT;
import static com.metsci.glimpse.core.gl.util.GLCapabilityUtils.getGLRendererString;
import static com.metsci.glimpse.core.gl.util.GLCapabilityUtils.getGLVersionString;
import static com.metsci.glimpse.core.support.DisposableUtils.onGLDispose;
import static com.metsci.glimpse.core.support.DisposableUtils.onGLInit;
import static com.metsci.glimpse.layers.misc.UiUtils.ensureAnimating;
import static com.metsci.glimpse.layers.misc.UiUtils.requireSwingThread;
import static com.metsci.glimpse.util.logging.LoggerUtils.getLogger;
import static com.metsci.glimpse.util.logging.LoggerUtils.logInfo;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.image.BufferedImage;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.swing.JPanel;
import com.jogamp.opengl.GLAnimatorControl;
import com.jogamp.opengl.GLContext;
import com.jogamp.opengl.GLProfile;
import com.metsci.glimpse.core.context.GlimpseContext;
import com.metsci.glimpse.core.support.swing.NewtSwingEDTGlimpseCanvas;
public abstract class GlimpseCanvasView extends View
{
private static final Logger logger = getLogger( GlimpseCanvasView.class );
/**
* Specifies the approach to use when moving a GL canvas from one parent component to another.
* This is exercised during changes to the docking arrangement (both programmatic and interactive).
* Recognized values are:
*
* - FAST: rely on NEWT reparenting -- smooth when it works, but breaks badly on some platforms
*
- ROBUST: avoid NEWT reparenting -- reliable across platforms, but can be slow and clunky
*
- AUTO: choose the best method, potentially based on the details of the current platform
*
* Defaults to AUTO if no value is specified, or if an unrecognized value is specified.
*/
public static final String glReparentingMethod = System.getProperty( "layers.glReparentingMethod" );
protected GLAnimatorControl animator;
protected boolean areTraitsSet;
protected boolean isCanvasReady;
public final JPanel canvasParent;
protected NewtSwingEDTGlimpseCanvas canvas;
/**
* Stores facets states during a reparent -- populated from old facets in the old canvas's
* onGLDispose(), then applied to new facets (and cleared) in the new canvas's onGLInit().
*/
protected final Map facetStatesBeforeReparent;
public GlimpseCanvasView( GLProfile glProfile, Collection extends ViewOption> options )
{
super( options );
this.animator = null;
this.areTraitsSet = false;
this.isCanvasReady = false;
this.canvas = null;
this.facetStatesBeforeReparent = new HashMap<>( );
// TODO: Consider platform details when method is AUTO
if ( equal( glReparentingMethod, "FAST" ) )
{
// NEWT's reparenting works fine on some platforms, and is smoother than the method below
this.canvasParent = new JPanel( new BorderLayout( ) );
this.setUpCanvas( glProfile );
}
else
{
// NEWT's reparenting seems to have problematic race conditions -- so remove all GL stuff
// before the view gets reparented, and re-create the GL stuff after reparenting is done
this.canvasParent = new JPanel( new BorderLayout( ) )
{
@Override
public void addNotify( )
{
super.addNotify( );
setUpCanvas( glProfile );
}
@Override
public void removeNotify( )
{
tearDownCanvas( );
super.removeNotify( );
}
};
}
}
protected void setUpCanvas( GLProfile glProfile )
{
if ( this.canvas == null )
{
SharedContextViewOption opt = null;
for ( ViewOption o : this.viewOptions )
{
if ( o instanceof SharedContextViewOption )
opt = ( SharedContextViewOption ) o;
}
if ( opt == null )
this.canvas = new NewtSwingEDTGlimpseCanvas( glProfile );
else
this.canvas = new NewtSwingEDTGlimpseCanvas( opt.context );
// Once canvas is ready, do view-specific setup and install facets
onGLInit( this.canvas, ( drawable ) ->
{
logInfo( logger, "GL canvas init: title = \"%s\", GL_VERSION = \"%s\", GL_RENDERER = \"%s\", reparenting = %s", this.title.v( ), getGLVersionString( drawable.getGL( ) ), getGLRendererString( drawable.getGL( ) ), firstNonNull( glReparentingMethod, "default" ) );
this.doContextReady( this.canvas.getGlimpseContext( ) );
this.isCanvasReady = true;
if ( this.areTraitsSet )
{
super.init( );
}
for ( Layer layer : this._layers.v( ) )
{
FacetState state = facetStatesBeforeReparent.get( layer );
this.installLayer( layer, state );
}
facetStatesBeforeReparent.clear( );
} );
// Before canvas gets destroyed, uninstall facets and do view-specific tear-down
onGLDispose( this.canvas, ( drawable ) ->
{
logInfo( logger, "GL canvas dispose: title = \"%s\"", this.title.v( ) );
for ( Layer layer : this._layers.v( ) )
{
// The GLContext is being disposed, so something must be happening to the view as a whole:
// either the view is being closed (in which case the value of isReinstall doesn't matter),
// or it is being re-parented (in which case we want isReinstall to be true)
boolean isReinstall = true;
FacetState state = this.uninstallLayer( layer, isReinstall );
facetStatesBeforeReparent.put( layer, state );
}
this.doContextDying( this.canvas.getGlimpseContext( ) );
this.isCanvasReady = false;
} );
this.canvasParent.add( this.canvas );
if ( this.animator != null )
{
ensureAnimating( this.animator );
this.animator.add( this.canvas.getGLDrawable( ) );
}
}
}
protected void tearDownCanvas( )
{
if ( this.canvas != null )
{
this.animator.remove( this.canvas.getGLDrawable( ) );
this.canvas.destroy( );
this.canvasParent.remove( this.canvas );
this.canvas = null;
}
}
/**
* Create layouts and painters, and add them to the canvas.
*/
protected abstract void doContextReady( GlimpseContext context );
/**
* Remove layouts and painters from the canvas, and dispose of them.
*/
protected abstract void doContextDying( GlimpseContext context );
@Override
public void setGLAnimator( GLAnimatorControl animator )
{
this.animator = animator;
if ( this.canvas != null )
{
ensureAnimating( this.animator );
this.animator.add( this.canvas.getGLDrawable( ) );
}
}
@Override
protected void init( )
{
this.areTraitsSet = true;
if ( this.isCanvasReady )
{
super.init( );
}
}
@Override
protected void installLayer( Layer layer, FacetState state )
{
if ( this.isCanvasReady )
{
super.installLayer( layer, state );
}
}
@Override
public Component getComponent( )
{
return this.canvasParent;
}
public void glimpseInvoke( GlimpseRunnable runnable )
{
requireSwingThread( );
glimpseRun( this.canvas.getGlimpseContext( ), runnable );
}
// TODO: This should go somewhere more general, once all its issues have been worked out
protected static void glimpseRun( GlimpseContext context, GlimpseRunnable runnable )
{
requireSwingThread( );
// TODO: Not sure what happens if context is not fully realized yet ... needs to be investigated, but will take some effort
GLContext glimpse = context.getGLContext( );
GLContext current = GLContext.getCurrent( );
if ( current == glimpse )
{
runnable.run( context );
}
else if ( current == null )
{
if ( glimpse.makeCurrent( ) == CONTEXT_NOT_CURRENT )
{
throw new RuntimeException( "Failed to make GLContext current in glimpseInvoke()" );
}
else
{
try
{
runnable.run( context );
}
finally
{
glimpse.release( );
}
}
}
else
{
try
{
if ( glimpse.makeCurrent( ) == CONTEXT_NOT_CURRENT )
{
throw new RuntimeException( "Failed to make GLContext current in glimpseInvoke()" );
}
else
{
runnable.run( context );
}
}
finally
{
if ( current.makeCurrent( ) == CONTEXT_NOT_CURRENT )
{
throw new RuntimeException( "Failed to restore original GLContext after glimpseInvoke()" );
}
}
}
}
public BufferedImage toBufferedImage( )
{
requireSwingThread( );
// TODO: Should this be using glimpseInvoke()? Or doing something completely different?
return canvas.toBufferedImage( );
}
@Override
protected void dispose( )
{
if ( this.canvas != null && this.animator != null )
{
this.animator.remove( this.canvas.getGLDrawable( ) );
}
super.dispose( );
if ( this.canvas != null )
{
this.tearDownCanvas( );
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy