|
@@ -1,497 +1,519 @@
|
|
|
-/*
|
|
|
- * Copyright (c) 2009-2021 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.Displays;
|
|
|
-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.FINE, "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, THREAD_NAME);
|
|
|
- renderThread.start();
|
|
|
- } else if (needClose.get()) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- logger.log(Level.FINE, "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.FINE, "EDT: OGL has created the display");
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void removeNotify() {
|
|
|
- if (needClose.get()) {
|
|
|
- logger.log(Level.FINE, "EDT: Application is stopped. Not restoring canvas.");
|
|
|
- super.removeNotify();
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // We must tell GL context to shut down and wait for it to
|
|
|
- // shut down. Otherwise, issues will occur.
|
|
|
- logger.log(Level.FINE, "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.FINE, "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;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void create(boolean waitFor) {
|
|
|
- if (renderThread == null) {
|
|
|
- logger.log(Level.FINE, "MAIN: Creating OGL thread.");
|
|
|
-
|
|
|
- renderThread = new Thread(LwjglCanvas.this, THREAD_NAME);
|
|
|
- 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.
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Canvas getCanvas() {
|
|
|
- return canvas;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- protected void runLoop() {
|
|
|
- if (desiredTask != TASK_NOTHING) {
|
|
|
- synchronized (taskLock) {
|
|
|
- switch (desiredTask) {
|
|
|
- case TASK_CREATE_DISPLAY:
|
|
|
- logger.log(Level.FINE, "OGL: Creating display ..");
|
|
|
- restoreCanvas();
|
|
|
- listener.gainFocus();
|
|
|
- desiredTask = TASK_NOTHING;
|
|
|
- break;
|
|
|
- case TASK_DESTROY_DISPLAY:
|
|
|
- logger.log(Level.FINE, "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.FINE, "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.FINE, "OGL: Creating display context ..");
|
|
|
-
|
|
|
- // Set renderable to true, since canvas is now displayable.
|
|
|
- renderable.set(true);
|
|
|
- createContext(settings);
|
|
|
-
|
|
|
- logger.log(Level.FINE, "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() {
|
|
|
- @Override
|
|
|
- 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>
|
|
|
- *
|
|
|
- * @param forPbuffer true→zero samples, false→correct number of samples
|
|
|
- * @return a new instance
|
|
|
- */
|
|
|
- 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(), settings.getAlphaBits(),
|
|
|
- settings.getDepthBits(), settings.getStencilBits(), 0, // samples
|
|
|
- 0, 0, 0, settings.useStereo3D());
|
|
|
- }
|
|
|
- return pbufferFormat;
|
|
|
- } else {
|
|
|
- if (canvasFormat == null) {
|
|
|
- int samples = getNumSamplesToUse();
|
|
|
- canvasFormat = new PixelFormat(settings.getBitsPerPixel(), settings.getAlphaBits(),
|
|
|
- settings.getDepthBits(), settings.getStencilBits(), samples, 0, 0, 0,
|
|
|
- settings.useStereo3D());
|
|
|
- }
|
|
|
- return canvasFormat;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Makes sure the pbuffer is available and ready for use
|
|
|
- *
|
|
|
- * @throws LWJGLException if the buffer can't be made current
|
|
|
- */
|
|
|
- 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.FINE, "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
|
|
|
- */
|
|
|
- @Override
|
|
|
- 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. In the first call of this method, OpenGL context is not ready yet. Therefore, OpenCL
|
|
|
- * context cannot be created. The second call of this method is done after "simpleInitApp" is
|
|
|
- * called. Therefore, OpenCL won't be available in "simpleInitApp" if Canvas/Swing is used. To use
|
|
|
- * OpenCL with Canvas/Swing, you need to use OpenCL in the rendering loop "simpleUpdate" and check
|
|
|
- * for "context.getOpenCLContext()!=null".
|
|
|
- */
|
|
|
- @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();
|
|
|
- allowSwapBuffers = settings.isSwapBuffers();
|
|
|
-
|
|
|
- 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));
|
|
|
- }
|
|
|
- if (settings.isOpenCLSupport()) {
|
|
|
- initOpenCL();
|
|
|
- }
|
|
|
-
|
|
|
- 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?)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Displays getDisplays() {
|
|
|
- // TODO Auto-generated method stub
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public int getPrimaryDisplay() {
|
|
|
- // TODO Auto-generated method stub
|
|
|
- return 0;
|
|
|
- }
|
|
|
-}
|
|
|
+/*
|
|
|
+ * Copyright (c) 2009-2021 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.Displays;
|
|
|
+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.FINE, "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, THREAD_NAME);
|
|
|
+ renderThread.start();
|
|
|
+ } else if (needClose.get()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.log(Level.FINE, "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.FINE, "EDT: OGL has created the display");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void removeNotify() {
|
|
|
+ if (needClose.get()) {
|
|
|
+ logger.log(Level.FINE, "EDT: Application is stopped. Not restoring canvas.");
|
|
|
+ super.removeNotify();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // We must tell GL context to shut down and wait for it to
|
|
|
+ // shut down. Otherwise, issues will occur.
|
|
|
+ logger.log(Level.FINE, "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.FINE, "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;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void create(boolean waitFor) {
|
|
|
+ if (renderThread == null) {
|
|
|
+ logger.log(Level.FINE, "MAIN: Creating OGL thread.");
|
|
|
+
|
|
|
+ renderThread = new Thread(LwjglCanvas.this, THREAD_NAME);
|
|
|
+ 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.
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Canvas getCanvas() {
|
|
|
+ return canvas;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void runLoop() {
|
|
|
+ if (desiredTask != TASK_NOTHING) {
|
|
|
+ synchronized (taskLock) {
|
|
|
+ switch (desiredTask) {
|
|
|
+ case TASK_CREATE_DISPLAY:
|
|
|
+ logger.log(Level.FINE, "OGL: Creating display ..");
|
|
|
+ restoreCanvas();
|
|
|
+ listener.gainFocus();
|
|
|
+ desiredTask = TASK_NOTHING;
|
|
|
+ break;
|
|
|
+ case TASK_DESTROY_DISPLAY:
|
|
|
+ logger.log(Level.FINE, "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.FINE, "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.FINE, "OGL: Creating display context ..");
|
|
|
+
|
|
|
+ // Set renderable to true, since canvas is now displayable.
|
|
|
+ renderable.set(true);
|
|
|
+ createContext(settings);
|
|
|
+
|
|
|
+ logger.log(Level.FINE, "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() {
|
|
|
+ @Override
|
|
|
+ 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>
|
|
|
+ *
|
|
|
+ * @param forPbuffer true→zero samples, false→correct number of samples
|
|
|
+ * @return a new instance
|
|
|
+ */
|
|
|
+ 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(),
|
|
|
+ settings.getAlphaBits(),
|
|
|
+ settings.getDepthBits(),
|
|
|
+ settings.getStencilBits(),
|
|
|
+ 0, // samples
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ settings.useStereo3D()
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return pbufferFormat;
|
|
|
+ } else {
|
|
|
+ if (canvasFormat == null) {
|
|
|
+ int samples = getNumSamplesToUse();
|
|
|
+ canvasFormat =
|
|
|
+ new PixelFormat(
|
|
|
+ settings.getBitsPerPixel(),
|
|
|
+ settings.getAlphaBits(),
|
|
|
+ settings.getDepthBits(),
|
|
|
+ settings.getStencilBits(),
|
|
|
+ samples,
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ settings.useStereo3D()
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return canvasFormat;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Makes sure the pbuffer is available and ready for use
|
|
|
+ *
|
|
|
+ * @throws LWJGLException if the buffer can't be made current
|
|
|
+ */
|
|
|
+ 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.FINE, "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
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ 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. In the first call of this method, OpenGL context is not ready yet. Therefore, OpenCL
|
|
|
+ * context cannot be created. The second call of this method is done after "simpleInitApp" is
|
|
|
+ * called. Therefore, OpenCL won't be available in "simpleInitApp" if Canvas/Swing is used. To use
|
|
|
+ * OpenCL with Canvas/Swing, you need to use OpenCL in the rendering loop "simpleUpdate" and check
|
|
|
+ * for "context.getOpenCLContext()!=null".
|
|
|
+ */
|
|
|
+ @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();
|
|
|
+ allowSwapBuffers = settings.isSwapBuffers();
|
|
|
+
|
|
|
+ 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));
|
|
|
+ }
|
|
|
+ if (settings.isOpenCLSupport()) {
|
|
|
+ initOpenCL();
|
|
|
+ }
|
|
|
+
|
|
|
+ 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?)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Displays getDisplays() {
|
|
|
+ // TODO Auto-generated method stub
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int getPrimaryDisplay() {
|
|
|
+ // TODO Auto-generated method stub
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+}
|