gov.nasa.worldwind.WorldWindowGLAutoDrawable Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2012 United States Government as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/
package gov.nasa.worldwind;
import com.jogamp.opengl.util.texture.TextureIO;
import gov.nasa.worldwind.cache.GpuResourceCache;
import gov.nasa.worldwind.event.*;
import gov.nasa.worldwind.exception.WWAbsentRequirementException;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.pick.*;
import gov.nasa.worldwind.render.ScreenCreditController;
import gov.nasa.worldwind.util.*;
import gov.nasa.worldwind.util.dashboard.DashboardController;
import com.jogamp.opengl.*;
import com.jogamp.opengl.awt.AWTGLAutoDrawable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.util.concurrent.*;
import java.util.logging.Level;
/**
* A non-platform specific {@link WorldWindow} class. This class can be aggregated into platform-specific classes to
* provide the core functionality of World Wind.
*
* @author Tom Gaskins
* @version $Id: WorldWindowGLAutoDrawable.java 2047 2014-06-06 22:48:33Z tgaskins $
*/
public class WorldWindowGLAutoDrawable extends WorldWindowImpl implements WorldWindowGLDrawable, GLEventListener {
/**
* Default time in milliseconds that the view must remain unchanged before the {@link View#VIEW_STOPPED} message is
* sent.
*/
public static final long DEFAULT_VIEW_STOP_TIME = 1000;
private GLAutoDrawable drawable;
private DashboardController dashboard;
private boolean shuttingDown = false;
private Timer redrawTimer = new Timer(Integer.MAX_VALUE, actionEvent -> redraw());
private boolean firstInit = true;
/**
* Time in milliseconds that the view must remain unchanged before the {@link View#VIEW_STOPPED} message is sent.
*/
protected long viewStopTime = DEFAULT_VIEW_STOP_TIME;
/**
* The most recent View modelView ID.
*
* @see gov.nasa.worldwind.View#getViewStateID()
*/
protected long lastViewID;
/**
* Schedule task to send the {@link View#VIEW_STOPPED} message after the view stop time elapses.
*/
protected ScheduledFuture viewRefreshTask;
protected boolean enableGpuCacheReinitialization = true;
/**
* Construct a new WorldWindowGLCanvas
for a specified {@link GLDrawable}.
*/
public WorldWindowGLAutoDrawable() {
SceneController sc = this.getSceneController();
if (sc != null)
sc.addPropertyChangeListener(this);
redrawTimer.setRepeats(false);
redrawTimer.setCoalesce(true);
}
/**
* Indicates the amount of time, in milliseconds, that the View must remain unchanged before a {@link
* View#VIEW_STOPPED} event is triggered.
*
* @return Time in milliseconds that the View must must remain unchanged before the view stopped event is
* triggered.
*/
public long getViewStopTime() {
return this.viewStopTime;
}
/**
* Specifies the amount of time, in milliseconds, that the View must remain unchanged before a {@link
* View#VIEW_STOPPED} event is triggered.
*
* @param time Time in milliseconds that the View must must remain unchanged before the view stopped event is
* triggered.
*/
public void setViewStopTime(long time) {
this.viewStopTime = time;
}
public void initDrawable(GLAutoDrawable glAutoDrawable) {
if (glAutoDrawable == null) {
String msg = Logging.getMessage("nullValue.DrawableIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
this.drawable = glAutoDrawable;
this.drawable.setAutoSwapBufferMode(false);
this.drawable.addGLEventListener(this);
}
@Override
public boolean isEnableGpuCacheReinitialization() {
return enableGpuCacheReinitialization;
}
@Override
public void setEnableGpuCacheReinitialization(boolean enableGpuCacheReinitialization) {
this.enableGpuCacheReinitialization = enableGpuCacheReinitialization;
}
public void initGpuResourceCache(GpuResourceCache cache) {
if (cache == null) {
String msg = Logging.getMessage("nullValue.GpuResourceCacheIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
this.setGpuResourceCache(cache);
}
public void endInitialization() {
initializeCreditsController();
this.dashboard = new DashboardController(this, (Component) this.drawable);
}
protected void initializeCreditsController() {
new ScreenCreditController((WorldWindow) this.drawable);
}
@Override
public void shutdown() {
this.shuttingDown = true;
this.redrawNow(); // Invokes a repaint, where the rest of the shutdown work is done.
}
protected void doShutdown() {
super.shutdown();
this.drawable.removeGLEventListener(this);
if (this.dashboard != null)
this.dashboard.dispose();
if (this.viewRefreshTask != null)
this.viewRefreshTask.cancel(false);
this.shuttingDown = false;
}
@Override
public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
if (propertyChangeEvent == null) {
String msg = Logging.getMessage("nullValue.PropertyChangeEventIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
this.redraw(); // Queue a JOGL display request.
}
public GLContext getContext() {
return this.drawable.getContext();
}
protected boolean isGLContextCompatible(GLContext context) {
return context != null && context.isGL2();
}
protected String[] getRequiredOglFunctions() {
return new String[]{"glActiveTexture", "glClientActiveTexture"};
}
protected String[] getRequiredOglExtensions() {
return new String[]{};
}
/**
* See {@link GLEventListener#init(GLAutoDrawable)}.
*
* @param glAutoDrawable the drawable
*/
public void init(GLAutoDrawable glAutoDrawable) {
if (!this.isGLContextCompatible(glAutoDrawable.getContext())) {
String msg = Logging.getMessage("WorldWindowGLAutoDrawable.IncompatibleGLContext",
glAutoDrawable.getContext());
this.callRenderingExceptionListeners(new WWAbsentRequirementException(msg));
}
for (String funcName : this.getRequiredOglFunctions()) {
if (!glAutoDrawable.getGL().isFunctionAvailable(funcName)) {
//noinspection ThrowableInstanceNeverThrown
this.callRenderingExceptionListeners(new WWAbsentRequirementException(funcName + " not available"));
}
}
for (String extName : this.getRequiredOglExtensions()) {
if (!glAutoDrawable.getGL().isExtensionAvailable(extName)) {
//noinspection ThrowableInstanceNeverThrown
this.callRenderingExceptionListeners(new WWAbsentRequirementException(extName + " not available"));
}
}
if (this.firstInit)
this.firstInit = false;
else if (this.enableGpuCacheReinitialization)
this.reinitialize(glAutoDrawable);
// Disables use of the OpenGL extension GL_ARB_texture_rectangle by JOGL's Texture creation utility.
//
// Between version 1.1.1 and version 2.x, JOGL modified its texture creation utility to favor
// GL_ARB_texture_rectangle over GL_ARB_texture_non_power_of_two on Mac OS X machines with ATI graphics cards. See
// the following URL for details on the texture rectangle extension: http://www.opengl.org/registry/specs/ARB/texture_rectangle.txt
//
// There are two problems with favoring texture rectangle for non power of two textures:
// 1) As of November 2012, we cannot find any evidence that the GL_ARB_texture_non_power_of_two extension is
// problematic on Mac OS X machines with ATI graphics cards. The texture rectangle extension is more limiting
// than the NPOT extension, and therefore not preferred.
// 2) World Wind assumes that a texture's target is always GL_TEXTURE_2D, and therefore incorrectly displays
// textures with the target GL_TEXTURE_RECTANGLE.
TextureIO.setTexRectEnabled(false);
// this.drawable.setGL(new DebugGL(this.drawable.getGL())); // uncomment to use the debug drawable
}
@SuppressWarnings({"UnusedParameters"})
protected void reinitialize(GLAutoDrawable glAutoDrawable) {
// Clear the gpu resource cache if the window is reinitializing, most likely with a new gl hardware context.
if (this.getGpuResourceCache() != null)
this.getGpuResourceCache().clear();
this.getSceneController().reinitialize();
}
/**
* See {@link GLEventListener#init(GLAutoDrawable)}.
*
* GLEventListener's dispose method indicates that the GL context has been released, and provides the listener an
* opportunity to clean up any resources. Dispose does not imply that the component's lifecycle has ended or that
* the application is closing. There are three cases in which dispose may be called:
*
* - The WorldWindow is removed from its parent component.
- The WorldWindow's parent frame is
* closed.
- The application calls either GLCanvas.dispose or GLJPanel.dispose.
*
* This implementation is left empty. In the case when a WorldWindow or a World Wind application has reached its end
* of life, its resources should be released by calling {@link gov.nasa.worldwind.WorldWindow#shutdown()} or {@link
* gov.nasa.worldwind.WorldWind#shutDown()}, respectively. In the case when a WorldWindow is removed from its parent
* frame or that frame is closed without a call to shutdown, it is assumed that the application intends to reuse the
* WorldWindow. Resources associated with the previous GL context are released in the subsequent call to init.
*
* @param glAutoDrawable the drawable
*/
@Override
public void dispose(GLAutoDrawable glAutoDrawable) {
}
/**
* See {@link GLEventListener#display(GLAutoDrawable)}.
*
* @param glAutoDrawable the drawable
* @throws IllegalStateException if no {@link SceneController} exists for this canvas
*/
public void display(GLAutoDrawable glAutoDrawable) {
// Performing shutdown here in order to do so with a current GL context for GL resource disposal.
if (this.shuttingDown) {
try {
this.doShutdown();
} catch (Exception e) {
Logging.logger().log(Level.SEVERE, Logging.getMessage(
"WorldWindowGLCanvas.ExceptionWhileShuttingDownWorldWindow"), e);
}
return;
}
try {
SceneController sc = this.getSceneController();
if (sc == null) {
String msg = Logging.getMessage("WorldWindowGLCanvas.ScnCntrllerNullOnRepaint");
Logging.logger().severe(msg);
throw new IllegalStateException(msg);
}
// Determine if the view has changed since the last frame.
this.checkForViewChange();
Position positionAtStart = this.getCurrentPosition();
PickedObject selectionAtStart = this.getCurrentSelection();
PickedObjectList boxSelectionAtStart = this.getCurrentBoxSelection();
try {
this.callRenderingListeners(new RenderingEvent(this.drawable, RenderingEvent.BEFORE_RENDERING));
} catch (Exception e) {
Logging.logger().log(Level.SEVERE,
Logging.getMessage("WorldWindowGLAutoDrawable.ExceptionDuringGLEventListenerDisplay"), e);
}
int redrawDelay = this.doDisplay();
if (redrawDelay > 0) {
if (!redrawTimer.isRunning()){
redrawTimer.setInitialDelay(redrawDelay);
redrawTimer.start();
}
}
try {
this.callRenderingListeners(new RenderingEvent(this.drawable, RenderingEvent.BEFORE_BUFFER_SWAP));
} catch (Exception e) {
Logging.logger().log(Level.SEVERE,
Logging.getMessage("WorldWindowGLAutoDrawable.ExceptionDuringGLEventListenerDisplay"), e);
}
this.doSwapBuffers(this.drawable);
Double frameTime = sc.getFrameTime();
if (frameTime != null)
this.setValue(PerformanceStatistic.FRAME_TIME, frameTime);
Double frameRate = sc.getFramesPerSecond();
if (frameRate != null)
this.setValue(PerformanceStatistic.FRAME_RATE, frameRate);
// Dispatch the rendering exceptions accumulated by the SceneController during this frame to our
// RenderingExceptionListeners.
Iterable renderingExceptions = sc.getRenderingExceptions();
if (renderingExceptions != null) {
for (Throwable t : renderingExceptions) {
if (t != null)
this.callRenderingExceptionListeners(t);
}
}
this.callRenderingListeners(new RenderingEvent(this.drawable, RenderingEvent.AFTER_BUFFER_SWAP));
// Position and selection notification occurs only on triggering conditions, not same-state conditions:
// start == null, end == null: nothing selected -- don't notify
// start == null, end != null: something now selected -- notify
// start != null, end == null: something was selected but no longer is -- notify
// start != null, end != null, start != end: something new was selected -- notify
// start != null, end != null, start == end: same thing is selected -- don't notify
Position positionAtEnd = this.getCurrentPosition();
if (positionAtStart != null || positionAtEnd != null) {
// call the listener if both are not null or positions are the same
if (positionAtStart != null && positionAtEnd != null) {
if (!positionAtStart.equals(positionAtEnd))
this.callPositionListeners(new PositionEvent(this.drawable, sc.getPickPoint(),
positionAtStart, positionAtEnd));
} else {
this.callPositionListeners(new PositionEvent(this.drawable, sc.getPickPoint(),
positionAtStart, positionAtEnd));
}
}
PickedObject selectionAtEnd = this.getCurrentSelection();
if (selectionAtStart != null || selectionAtEnd != null) {
this.callSelectListeners(new SelectEvent(this.drawable, SelectEvent.ROLLOVER,
sc.getPickPoint(), sc.getPickedObjectList()));
}
PickedObjectList boxSelectionAtEnd = this.getCurrentBoxSelection();
if (boxSelectionAtStart != null || boxSelectionAtEnd != null) {
this.callSelectListeners(new SelectEvent(this.drawable, SelectEvent.BOX_ROLLOVER,
sc.getPickRectangle(), sc.getObjectsInPickRectangle()));
}
} catch (Exception e) {
Logging.logger().log(Level.SEVERE, Logging.getMessage(
"WorldWindowGLCanvas.ExceptionAttemptingRepaintWorldWindow"), e);
}
}
/**
* Determine if the view has changed since the previous frame. If the view has changed, schedule a task that will
* send a {@link View#VIEW_STOPPED} to the Model if the view does not change for {@link #viewStopTime}
* milliseconds.
*
* @see #getViewStopTime()
*/
protected void checkForViewChange() {
long viewId = this.getView().getViewStateID();
// Determine if the view has changed since the previous frame.
if (viewId != this.lastViewID) {
// View has changed, capture the new viewStateID
this.lastViewID = viewId;
// Cancel the previous view stop task and schedule a new one because the view has changed.
this.scheduleViewStopTask(this.getViewStopTime());
}
}
/**
* Performs the actual repaint. Provided so that subclasses may override the repaint steps.
*
* @return if greater than zero, the window should be automatically repainted again at the indicated number of
* milliseconds from this method's return.
*/
protected int doDisplay() {
return this.getSceneController().repaint();
}
/**
* Performs the actual buffer swap. Provided so that subclasses may override the swap steps.
*
* @param drawable the window's associated drawable.
*/
protected void doSwapBuffers(GLAutoDrawable drawable) {
drawable.swapBuffers();
}
/**
* See {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int)}.
*
* @param glAutoDrawable the drawable
*/
public void reshape(GLAutoDrawable glAutoDrawable, int x, int y, int w, int h) {
// This is apparently necessary to enable the WWJ canvas to resize correctly with JSplitPane.
((Component) glAutoDrawable).setMinimumSize(new Dimension(0, 0));
}
@Override
public void redraw() {
if (this.drawable != null)
((AWTGLAutoDrawable) this.drawable).repaint();
}
public void redrawNow() {
if (this.drawable != null)
this.drawable.display();
}
/**
* Schedule a task that will send a {@link View#VIEW_STOPPED} message to the Model when the task executes. If the
* task runs (is not cancelled), then the view is considered stopped. Only one view stop task is scheduled at a
* time. If this method is called again before the task executes, the task will be cancelled and a new task
* scheduled.
*
* @param delay Delay in milliseconds until the task runs.
*/
protected void scheduleViewStopTask(long delay) {
Runnable viewStoppedTask = new Runnable() {
public void run() {
// Call onMessage on the EDT with a VIEW_STOP message
EventQueue.invokeLater(new Runnable() {
public void run() {
WorldWindowGLAutoDrawable.this.onMessage(
new Message(View.VIEW_STOPPED, WorldWindowGLAutoDrawable.this));
}
});
}
};
// Cancel the previous view stop task
if (this.viewRefreshTask != null) {
this.viewRefreshTask.cancel(false);
}
// Schedule the task for execution in delay milliseconds
this.viewRefreshTask = WorldWind.getScheduledTaskService()
.addScheduledTask(viewStoppedTask, delay, TimeUnit.MILLISECONDS);
}
/**
* {@inheritDoc}
*
* Forward the message event to the Model for distribution to the layers.
*
* @param msg Message event.
*/
@Override
public void onMessage(Message msg) {
Model model = this.getModel();
if (model != null) {
model.onMessage(msg);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy