blob: bb185433dca67403b2cb7e80bf30274290cd033b [file] [log] [blame]
/*
* Copyright (c) 2009-2010 jMonkeyEngine
* 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 'jMonkeyEngine' 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 THE COPYRIGHT OWNER OR
* CONTRIBUTORS 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.jme3.system.lwjgl;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeCanvasContext;
import com.jme3.system.JmeContext.Type;
import com.jme3.system.JmeSystem;
import com.jme3.system.Platform;
import java.awt.Canvas;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.Pbuffer;
import org.lwjgl.opengl.PixelFormat;
public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContext {
protected static final int TASK_NOTHING = 0,
TASK_DESTROY_DISPLAY = 1,
TASK_CREATE_DISPLAY = 2,
TASK_COMPLETE = 3;
// protected static final boolean USE_SHARED_CONTEXT =
// Boolean.parseBoolean(System.getProperty("jme3.canvas.sharedctx", "true"));
protected static final boolean USE_SHARED_CONTEXT = false;
private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName());
private Canvas canvas;
private int width;
private int height;
private final Object taskLock = new Object();
private int desiredTask = TASK_NOTHING;
private Thread renderThread;
private boolean runningFirstTime = true;
private boolean mouseWasGrabbed = false;
private boolean mouseWasCreated = false;
private boolean keyboardWasCreated = false;
private Pbuffer pbuffer;
private PixelFormat pbufferFormat;
private PixelFormat canvasFormat;
private class GLCanvas extends Canvas {
@Override
public void addNotify(){
super.addNotify();
if (renderThread != null && renderThread.getState() == Thread.State.TERMINATED)
return; // already destroyed.
if (renderThread == null){
logger.log(Level.INFO, "EDT: Creating OGL thread.");
// Also set some settings on the canvas here.
// So we don't do it outside the AWT thread.
canvas.setFocusable(true);
canvas.setIgnoreRepaint(true);
renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread");
renderThread.start();
}else if (needClose.get()){
return;
}
logger.log(Level.INFO, "EDT: Telling OGL to create display ..");
synchronized (taskLock){
desiredTask = TASK_CREATE_DISPLAY;
// while (desiredTask != TASK_COMPLETE){
// try {
// taskLock.wait();
// } catch (InterruptedException ex) {
// return;
// }
// }
// desiredTask = TASK_NOTHING;
}
// logger.log(Level.INFO, "EDT: OGL has created the display");
}
@Override
public void removeNotify(){
if (needClose.get()){
logger.log(Level.INFO, "EDT: Application is stopped. Not restoring canvas.");
super.removeNotify();
return;
}
// We must tell GL context to shutdown and wait for it to
// shutdown, otherwise, issues will occur.
logger.log(Level.INFO, "EDT: Telling OGL to destroy display ..");
synchronized (taskLock){
desiredTask = TASK_DESTROY_DISPLAY;
while (desiredTask != TASK_COMPLETE){
try {
taskLock.wait();
} catch (InterruptedException ex){
super.removeNotify();
return;
}
}
desiredTask = TASK_NOTHING;
}
logger.log(Level.INFO, "EDT: Acknowledged receipt of canvas death");
// GL context is dead at this point
super.removeNotify();
}
}
public LwjglCanvas(){
super();
canvas = new GLCanvas();
}
@Override
public Type getType() {
return Type.Canvas;
}
public void create(boolean waitFor){
if (renderThread == null){
logger.log(Level.INFO, "MAIN: Creating OGL thread.");
renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread");
renderThread.start();
}
// do not do anything.
// superclass's create() will be called at initInThread()
if (waitFor)
waitFor(true);
}
@Override
public void setTitle(String title) {
}
@Override
public void restart() {
frameRate = settings.getFrameRate();
// TODO: Handle other cases, like change of pixel format, etc.
}
public Canvas getCanvas(){
return canvas;
}
@Override
protected void runLoop(){
if (desiredTask != TASK_NOTHING){
synchronized (taskLock){
switch (desiredTask){
case TASK_CREATE_DISPLAY:
logger.log(Level.INFO, "OGL: Creating display ..");
restoreCanvas();
listener.gainFocus();
desiredTask = TASK_NOTHING;
break;
case TASK_DESTROY_DISPLAY:
logger.log(Level.INFO, "OGL: Destroying display ..");
listener.loseFocus();
pauseCanvas();
break;
}
desiredTask = TASK_COMPLETE;
taskLock.notifyAll();
}
}
if (renderable.get()){
int newWidth = Math.max(canvas.getWidth(), 1);
int newHeight = Math.max(canvas.getHeight(), 1);
if (width != newWidth || height != newHeight){
width = newWidth;
height = newHeight;
if (listener != null){
listener.reshape(width, height);
}
}
}else{
if (frameRate <= 0){
// NOTE: MUST be done otherwise
// Windows OS will freeze
Display.sync(30);
}
}
super.runLoop();
}
private void pauseCanvas(){
if (Mouse.isCreated()){
if (Mouse.isGrabbed()){
Mouse.setGrabbed(false);
mouseWasGrabbed = true;
}
mouseWasCreated = true;
Mouse.destroy();
}
if (Keyboard.isCreated()){
keyboardWasCreated = true;
Keyboard.destroy();
}
renderable.set(false);
destroyContext();
}
/**
* Called to restore the canvas.
*/
private void restoreCanvas(){
logger.log(Level.INFO, "OGL: Waiting for canvas to become displayable..");
while (!canvas.isDisplayable()){
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
logger.log(Level.SEVERE, "OGL: Interrupted! ", ex);
}
}
logger.log(Level.INFO, "OGL: Creating display context ..");
// Set renderable to true, since canvas is now displayable.
renderable.set(true);
createContext(settings);
logger.log(Level.INFO, "OGL: Display is active!");
try {
if (mouseWasCreated){
Mouse.create();
if (mouseWasGrabbed){
Mouse.setGrabbed(true);
mouseWasGrabbed = false;
}
}
if (keyboardWasCreated){
Keyboard.create();
keyboardWasCreated = false;
}
} catch (LWJGLException ex){
logger.log(Level.SEVERE, "Encountered exception when restoring input", ex);
}
SwingUtilities.invokeLater(new Runnable(){
public void run(){
canvas.requestFocus();
}
});
}
/**
* It seems it is best to use one pixel format for all shared contexts.
* @see <a href="http://developer.apple.com/library/mac/#qa/qa1248/_index.html">http://developer.apple.com/library/mac/#qa/qa1248/_index.html</a>
*/
protected PixelFormat acquirePixelFormat(boolean forPbuffer){
if (forPbuffer){
// Use 0 samples for pbuffer format, prevents
// crashes on bad drivers
if (pbufferFormat == null){
pbufferFormat = new PixelFormat(settings.getBitsPerPixel(),
0,
settings.getDepthBits(),
settings.getStencilBits(),
0);
}
return pbufferFormat;
}else{
if (canvasFormat == null){
int samples = 0;
if (settings.getSamples() > 1){
samples = settings.getSamples();
}
canvasFormat = new PixelFormat(settings.getBitsPerPixel(),
0,
settings.getDepthBits(),
settings.getStencilBits(),
samples);
}
return canvasFormat;
}
}
/**
* Makes sure the pbuffer is available and ready for use
*/
protected void makePbufferAvailable() throws LWJGLException{
if (pbuffer != null && pbuffer.isBufferLost()){
logger.log(Level.WARNING, "PBuffer was lost!");
pbuffer.destroy();
pbuffer = null;
}
if (pbuffer == null) {
pbuffer = new Pbuffer(1, 1, acquirePixelFormat(true), null);
pbuffer.makeCurrent();
logger.log(Level.INFO, "OGL: Pbuffer has been created");
// Any created objects are no longer valid
if (!runningFirstTime){
renderer.resetGLObjects();
}
}
pbuffer.makeCurrent();
if (!pbuffer.isCurrent()){
throw new LWJGLException("Pbuffer cannot be made current");
}
}
protected void destroyPbuffer(){
if (pbuffer != null){
if (!pbuffer.isBufferLost()){
pbuffer.destroy();
}
pbuffer = null;
}
}
/**
* This is called:
* 1) When the context thread ends
* 2) Any time the canvas becomes non-displayable
*/
protected void destroyContext(){
try {
// invalidate the state so renderer can resume operation
if (!USE_SHARED_CONTEXT){
renderer.cleanup();
}
if (Display.isCreated()){
/* FIXES:
* org.lwjgl.LWJGLException: X Error
* BadWindow (invalid Window parameter) request_code: 2 minor_code: 0
*
* Destroying keyboard early prevents the error above, triggered
* by destroying keyboard in by Display.destroy() or Display.setParent(null).
* Therefore Keyboard.destroy() should precede any of these calls.
*/
if (Keyboard.isCreated()){
// Should only happen if called in
// LwjglAbstractDisplay.deinitInThread().
Keyboard.destroy();
}
//try {
// NOTE: On Windows XP, not calling setParent(null)
// freezes the application.
// On Mac it freezes the application.
// On Linux it fixes a crash with X Window System.
if (JmeSystem.getPlatform() == Platform.Windows32
|| JmeSystem.getPlatform() == Platform.Windows64){
//Display.setParent(null);
}
//} catch (LWJGLException ex) {
// logger.log(Level.SEVERE, "Encountered exception when setting parent to null", ex);
//}
Display.destroy();
}
// The canvas is no longer visible,
// but the context thread is still running.
if (!needClose.get()){
// MUST make sure there's still a context current here ..
// Display is dead, make pbuffer available to the system
makePbufferAvailable();
renderer.invalidateState();
}else{
// The context thread is no longer running.
// Destroy pbuffer.
destroyPbuffer();
}
} catch (LWJGLException ex) {
listener.handleError("Failed make pbuffer available", ex);
}
}
/**
* This is called:
* 1) When the context thread starts
* 2) Any time the canvas becomes displayable again.
*/
@Override
protected void createContext(AppSettings settings) {
// In case canvas is not visible, we still take framerate
// from settings to prevent "100% CPU usage"
frameRate = settings.getFrameRate();
try {
if (renderable.get()){
if (!runningFirstTime){
// because the display is a different opengl context
// must reset the context state.
if (!USE_SHARED_CONTEXT){
renderer.cleanup();
}
}
// if the pbuffer is currently active,
// make sure to deactivate it
destroyPbuffer();
if (Keyboard.isCreated()){
Keyboard.destroy();
}
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
Display.setVSyncEnabled(settings.isVSync());
Display.setParent(canvas);
if (USE_SHARED_CONTEXT){
Display.create(acquirePixelFormat(false), pbuffer);
}else{
Display.create(acquirePixelFormat(false));
}
renderer.invalidateState();
}else{
// First create the pbuffer, if it is needed.
makePbufferAvailable();
}
// At this point, the OpenGL context is active.
if (runningFirstTime){
// THIS is the part that creates the renderer.
// It must always be called, now that we have the pbuffer workaround.
initContextFirstTime();
runningFirstTime = false;
}
} catch (LWJGLException ex) {
listener.handleError("Failed to initialize OpenGL context", ex);
// TODO: Fix deadlock that happens after the error (throw runtime exception?)
}
}
}