Home | History | Annotate | Download | only in lwjgl
      1 /*******************************************************************************
      2  * Copyright 2011 See AUTHORS file.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *   http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  ******************************************************************************/
     16 
     17 package com.badlogic.gdx.backends.lwjgl;
     18 
     19 import java.awt.Canvas;
     20 import java.awt.Cursor;
     21 import java.awt.Dimension;
     22 import java.awt.EventQueue;
     23 import java.util.HashMap;
     24 import java.util.Map;
     25 
     26 import org.lwjgl.opengl.AWTGLCanvas;
     27 import org.lwjgl.opengl.Display;
     28 
     29 import com.badlogic.gdx.Application;
     30 import com.badlogic.gdx.ApplicationListener;
     31 import com.badlogic.gdx.Audio;
     32 import com.badlogic.gdx.Files;
     33 import com.badlogic.gdx.Gdx;
     34 import com.badlogic.gdx.Graphics;
     35 import com.badlogic.gdx.Input;
     36 import com.badlogic.gdx.LifecycleListener;
     37 import com.badlogic.gdx.Net;
     38 import com.badlogic.gdx.Preferences;
     39 import com.badlogic.gdx.backends.lwjgl.audio.OpenALAudio;
     40 import com.badlogic.gdx.utils.Array;
     41 import com.badlogic.gdx.utils.Clipboard;
     42 import com.badlogic.gdx.utils.SharedLibraryLoader;
     43 
     44 /** An OpenGL surface on an AWT Canvas, allowing OpenGL to be embedded in a Swing application. This uses
     45  * {@link Display#setParent(Canvas)}, which is preferred over {@link AWTGLCanvas} but is limited to a single LwjglCanvas in an
     46  * application. All OpenGL calls are done on the EDT. Note that you may need to call {@link #stop()} or a Swing application may
     47  * deadlock on System.exit due to how LWJGL and/or Swing deal with shutdown hooks.
     48  * @author Nathan Sweet */
     49 public class LwjglCanvas implements Application {
     50 	static boolean isWindows = System.getProperty("os.name").contains("Windows");
     51 
     52 	LwjglGraphics graphics;
     53 	OpenALAudio audio;
     54 	LwjglFiles files;
     55 	LwjglInput input;
     56 	LwjglNet net;
     57 	ApplicationListener listener;
     58 	Canvas canvas;
     59 	final Array<Runnable> runnables = new Array();
     60 	final Array<Runnable> executedRunnables = new Array();
     61 	final Array<LifecycleListener> lifecycleListeners = new Array<LifecycleListener>();
     62 	boolean running = true;
     63 	int logLevel = LOG_INFO;
     64 	Cursor cursor;
     65 
     66 	public LwjglCanvas (ApplicationListener listener) {
     67 		LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
     68 		initialize(listener, config);
     69 	}
     70 
     71 	public LwjglCanvas (ApplicationListener listener, LwjglApplicationConfiguration config) {
     72 		initialize(listener, config);
     73 	}
     74 
     75 	private void initialize (ApplicationListener listener, LwjglApplicationConfiguration config) {
     76 		LwjglNativesLoader.load();
     77 
     78 		canvas = new Canvas() {
     79 			private final Dimension minSize = new Dimension(1, 1);
     80 
     81 			public final void addNotify () {
     82 				super.addNotify();
     83 				if (SharedLibraryLoader.isMac) {
     84 					EventQueue.invokeLater(new Runnable() {
     85 						public void run () {
     86 							create();
     87 						}
     88 					});
     89 				} else
     90 					create();
     91 			}
     92 
     93 			public final void removeNotify () {
     94 				stop();
     95 				super.removeNotify();
     96 			}
     97 
     98 			public Dimension getMinimumSize () {
     99 				return minSize;
    100 			}
    101 		};
    102 		canvas.setSize(1, 1);
    103 		canvas.setIgnoreRepaint(true);
    104 
    105 		graphics = new LwjglGraphics(canvas, config) {
    106 			public void setTitle (String title) {
    107 				super.setTitle(title);
    108 				LwjglCanvas.this.setTitle(title);
    109 			}
    110 
    111 			public boolean setWindowedMode (int width, int height, boolean fullscreen) {
    112 				if (!super.setWindowedMode(width, height)) return false;
    113 				if (!fullscreen) LwjglCanvas.this.setDisplayMode(width, height);
    114 				return true;
    115 			}
    116 
    117 			public boolean setFullscreenMode (DisplayMode displayMode) {
    118 				if (!super.setFullscreenMode(displayMode)) return false;
    119 				LwjglCanvas.this.setDisplayMode(displayMode.width, displayMode.height);
    120 				return true;
    121 			}
    122 		};
    123 		graphics.setVSync(config.vSyncEnabled);
    124 		if (!LwjglApplicationConfiguration.disableAudio) audio = new OpenALAudio();
    125 		files = new LwjglFiles();
    126 		input = new LwjglInput();
    127 		net = new LwjglNet();
    128 		this.listener = listener;
    129 
    130 		Gdx.app = this;
    131 		Gdx.graphics = graphics;
    132 		Gdx.audio = audio;
    133 		Gdx.files = files;
    134 		Gdx.input = input;
    135 		Gdx.net = net;
    136 	}
    137 
    138 	protected void setDisplayMode (int width, int height) {
    139 	}
    140 
    141 	protected void setTitle (String title) {
    142 	}
    143 
    144 	@Override
    145 	public ApplicationListener getApplicationListener () {
    146 		return listener;
    147 	}
    148 
    149 	public Canvas getCanvas () {
    150 		return canvas;
    151 	}
    152 
    153 	@Override
    154 	public Audio getAudio () {
    155 		return audio;
    156 	}
    157 
    158 	@Override
    159 	public Files getFiles () {
    160 		return files;
    161 	}
    162 
    163 	@Override
    164 	public Graphics getGraphics () {
    165 		return graphics;
    166 	}
    167 
    168 	@Override
    169 	public Input getInput () {
    170 		return input;
    171 	}
    172 
    173 	@Override
    174 	public Net getNet () {
    175 		return net;
    176 	}
    177 
    178 	@Override
    179 	public ApplicationType getType () {
    180 		return ApplicationType.Desktop;
    181 	}
    182 
    183 	@Override
    184 	public int getVersion () {
    185 		return 0;
    186 	}
    187 
    188 	void create () {
    189 		try {
    190 			graphics.setupDisplay();
    191 
    192 			listener.create();
    193 			listener.resize(Math.max(1, graphics.getWidth()), Math.max(1, graphics.getHeight()));
    194 
    195 			start();
    196 		} catch (Exception ex) {
    197 			stopped();
    198 			exception(ex);
    199 			return;
    200 		}
    201 
    202 		EventQueue.invokeLater(new Runnable() {
    203 			int lastWidth = Math.max(1, graphics.getWidth());
    204 			int lastHeight = Math.max(1, graphics.getHeight());
    205 
    206 			public void run () {
    207 				if (!running || Display.isCloseRequested()) {
    208 					running = false;
    209 					stopped();
    210 					return;
    211 				}
    212 				try {
    213 					Display.processMessages();
    214 					if (cursor != null || !isWindows) canvas.setCursor(cursor);
    215 
    216 					boolean shouldRender = false;
    217 
    218 					int width = Math.max(1, graphics.getWidth());
    219 					int height = Math.max(1, graphics.getHeight());
    220 					if (lastWidth != width || lastHeight != height) {
    221 						lastWidth = width;
    222 						lastHeight = height;
    223 						Gdx.gl.glViewport(0, 0, lastWidth, lastHeight);
    224 						resize(width, height);
    225 						listener.resize(width, height);
    226 						shouldRender = true;
    227 					}
    228 
    229 					if (executeRunnables()) shouldRender = true;
    230 
    231 					// If one of the runnables set running to false, for example after an exit().
    232 					if (!running) return;
    233 
    234 					input.update();
    235 					shouldRender |= graphics.shouldRender();
    236 					input.processEvents();
    237 					if (audio != null) audio.update();
    238 
    239 					if (shouldRender) {
    240 						graphics.updateTime();
    241 						graphics.frameId++;
    242 						listener.render();
    243 						Display.update(false);
    244 					}
    245 
    246 					Display.sync(getFrameRate());
    247 				} catch (Throwable ex) {
    248 					exception(ex);
    249 				}
    250 				EventQueue.invokeLater(this);
    251 			}
    252 		});
    253 	}
    254 
    255 	public boolean executeRunnables () {
    256 		synchronized (runnables) {
    257 			for (int i = runnables.size - 1; i >= 0; i--)
    258 				executedRunnables.addAll(runnables.get(i));
    259 			runnables.clear();
    260 		}
    261 		if (executedRunnables.size == 0) return false;
    262 		do
    263 			executedRunnables.pop().run();
    264 		while (executedRunnables.size > 0);
    265 		return true;
    266 	}
    267 
    268 	protected int getFrameRate () {
    269 		int frameRate = Display.isActive() ? graphics.config.foregroundFPS : graphics.config.backgroundFPS;
    270 		if (frameRate == -1) frameRate = 10;
    271 		if (frameRate == 0) frameRate = graphics.config.backgroundFPS;
    272 		if (frameRate == 0) frameRate = 30;
    273 		return frameRate;
    274 	}
    275 
    276 	protected void exception (Throwable ex) {
    277 		ex.printStackTrace();
    278 		stop();
    279 	}
    280 
    281 	/** Called after {@link ApplicationListener} create and resize, but before the game loop iteration. */
    282 	protected void start () {
    283 	}
    284 
    285 	/** Called when the canvas size changes. */
    286 	protected void resize (int width, int height) {
    287 	}
    288 
    289 	/** Called when the game loop has stopped. */
    290 	protected void stopped () {
    291 	}
    292 
    293 	public void stop () {
    294 		EventQueue.invokeLater(new Runnable() {
    295 			public void run () {
    296 				if (!running) return;
    297 				running = false;
    298 				try {
    299 					Display.destroy();
    300 					if (audio != null) audio.dispose();
    301 				} catch (Throwable ignored) {
    302 				}
    303 				Array<LifecycleListener> listeners = lifecycleListeners;
    304 				synchronized (listeners) {
    305 					for (LifecycleListener listener : listeners) {
    306 						listener.pause();
    307 						listener.dispose();
    308 					}
    309 				}
    310 				listener.pause();
    311 				listener.dispose();
    312 			}
    313 		});
    314 	}
    315 
    316 	@Override
    317 	public long getJavaHeap () {
    318 		return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    319 	}
    320 
    321 	@Override
    322 	public long getNativeHeap () {
    323 		return getJavaHeap();
    324 	}
    325 
    326 	Map<String, Preferences> preferences = new HashMap<String, Preferences>();
    327 
    328 	@Override
    329 	public Preferences getPreferences (String name) {
    330 		if (preferences.containsKey(name)) {
    331 			return preferences.get(name);
    332 		} else {
    333 			Preferences prefs = new LwjglPreferences(name, ".prefs/");
    334 			preferences.put(name, prefs);
    335 			return prefs;
    336 		}
    337 	}
    338 
    339 	@Override
    340 	public Clipboard getClipboard () {
    341 		return new LwjglClipboard();
    342 	}
    343 
    344 	@Override
    345 	public void postRunnable (Runnable runnable) {
    346 		synchronized (runnables) {
    347 			runnables.add(runnable);
    348 			Gdx.graphics.requestRendering();
    349 		}
    350 	}
    351 
    352 	@Override
    353 	public void debug (String tag, String message) {
    354 		if (logLevel >= LOG_DEBUG) {
    355 			System.out.println(tag + ": " + message);
    356 		}
    357 	}
    358 
    359 	@Override
    360 	public void debug (String tag, String message, Throwable exception) {
    361 		if (logLevel >= LOG_DEBUG) {
    362 			System.out.println(tag + ": " + message);
    363 			exception.printStackTrace(System.out);
    364 		}
    365 	}
    366 
    367 	public void log (String tag, String message) {
    368 		if (logLevel >= LOG_INFO) {
    369 			System.out.println(tag + ": " + message);
    370 		}
    371 	}
    372 
    373 	@Override
    374 	public void log (String tag, String message, Throwable exception) {
    375 		if (logLevel >= LOG_INFO) {
    376 			System.out.println(tag + ": " + message);
    377 			exception.printStackTrace(System.out);
    378 		}
    379 	}
    380 
    381 	@Override
    382 	public void error (String tag, String message) {
    383 		if (logLevel >= LOG_ERROR) {
    384 			System.err.println(tag + ": " + message);
    385 		}
    386 	}
    387 
    388 	@Override
    389 	public void error (String tag, String message, Throwable exception) {
    390 		if (logLevel >= LOG_ERROR) {
    391 			System.err.println(tag + ": " + message);
    392 			exception.printStackTrace(System.err);
    393 		}
    394 	}
    395 
    396 	@Override
    397 	public void setLogLevel (int logLevel) {
    398 		this.logLevel = logLevel;
    399 	}
    400 
    401 	@Override
    402 	public int getLogLevel () {
    403 		return logLevel;
    404 	}
    405 
    406 	@Override
    407 	public void exit () {
    408 		postRunnable(new Runnable() {
    409 			@Override
    410 			public void run () {
    411 				LwjglCanvas.this.listener.pause();
    412 				LwjglCanvas.this.listener.dispose();
    413 				if (audio != null) audio.dispose();
    414 				System.exit(-1);
    415 			}
    416 		});
    417 	}
    418 
    419 	/** @param cursor May be null. */
    420 	public void setCursor (Cursor cursor) {
    421 		this.cursor = cursor;
    422 	}
    423 
    424 	@Override
    425 	public void addLifecycleListener (LifecycleListener listener) {
    426 		synchronized (lifecycleListeners) {
    427 			lifecycleListeners.add(listener);
    428 		}
    429 	}
    430 
    431 	@Override
    432 	public void removeLifecycleListener (LifecycleListener listener) {
    433 		synchronized (lifecycleListeners) {
    434 			lifecycleListeners.removeValue(listener, true);
    435 		}
    436 	}
    437 }
    438