Home | History | Annotate | Download | only in lwjgl3
      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.lwjgl3;
     18 
     19 import java.io.File;
     20 
     21 import com.badlogic.gdx.graphics.glutils.GLVersion;
     22 import org.lwjgl.glfw.GLFW;
     23 import org.lwjgl.glfw.GLFWErrorCallback;
     24 import org.lwjgl.glfw.GLFWVidMode;
     25 import org.lwjgl.opengl.GL;
     26 import org.lwjgl.opengl.GL11;
     27 
     28 import com.badlogic.gdx.Application;
     29 import com.badlogic.gdx.ApplicationListener;
     30 import com.badlogic.gdx.Audio;
     31 import com.badlogic.gdx.Files;
     32 import com.badlogic.gdx.Gdx;
     33 import com.badlogic.gdx.Graphics;
     34 import com.badlogic.gdx.Input;
     35 import com.badlogic.gdx.LifecycleListener;
     36 import com.badlogic.gdx.Net;
     37 import com.badlogic.gdx.Preferences;
     38 import com.badlogic.gdx.backends.lwjgl3.audio.OpenALAudio;
     39 import com.badlogic.gdx.backends.lwjgl3.audio.mock.MockAudio;
     40 import com.badlogic.gdx.utils.Array;
     41 import com.badlogic.gdx.utils.Clipboard;
     42 import com.badlogic.gdx.utils.GdxRuntimeException;
     43 import com.badlogic.gdx.utils.ObjectMap;
     44 import com.badlogic.gdx.utils.SharedLibraryLoader;
     45 
     46 public class Lwjgl3Application implements Application {
     47 	private final Lwjgl3ApplicationConfiguration config;
     48 	private final Array<Lwjgl3Window> windows = new Array<Lwjgl3Window>();
     49 	private volatile Lwjgl3Window currentWindow;
     50 	private Audio audio;
     51 	private final Files files;
     52 	private final Net net;
     53 	private final ObjectMap<String, Preferences> preferences = new ObjectMap<String, Preferences>();
     54 	private final Lwjgl3Clipboard clipboard;
     55 	private int logLevel = LOG_INFO;
     56 	private volatile boolean running = true;
     57 	private final Array<Runnable> runnables = new Array<Runnable>();
     58 	private final Array<Runnable> executedRunnables = new Array<Runnable>();
     59 	private final Array<LifecycleListener> lifecycleListeners = new Array<LifecycleListener>();
     60 	private static GLFWErrorCallback errorCallback;
     61 	private static GLVersion glVersion;
     62 
     63 	static void initializeGlfw() {
     64 		if (errorCallback == null) {
     65 			Lwjgl3NativesLoader.load();
     66 			errorCallback = GLFWErrorCallback.createPrint(System.err);
     67 			GLFW.glfwSetErrorCallback(errorCallback);
     68 			if (GLFW.glfwInit() != GLFW.GLFW_TRUE) {
     69 				throw new GdxRuntimeException("Unable to initialize GLFW");
     70 			}
     71 		}
     72 	}
     73 
     74 	public Lwjgl3Application(ApplicationListener listener, Lwjgl3ApplicationConfiguration config) {
     75 		initializeGlfw();
     76 		this.config = Lwjgl3ApplicationConfiguration.copy(config);
     77 		if (this.config.title == null) this.config.title = listener.getClass().getSimpleName();
     78 		Gdx.app = this;
     79 		if (!config.disableAudio) {
     80 			try {
     81 				this.audio = Gdx.audio = new OpenALAudio(config.audioDeviceSimultaneousSources,
     82 						config.audioDeviceBufferCount, config.audioDeviceBufferSize);
     83 			} catch (Throwable t) {
     84 				log("Lwjgl3Application", "Couldn't initialize audio, disabling audio", t);
     85 				this.audio = Gdx.audio = new MockAudio();
     86 			}
     87 		} else {
     88 			this.audio = Gdx.audio = new MockAudio();
     89 		}
     90 		this.files = Gdx.files = new Lwjgl3Files();
     91 		this.net = Gdx.net = new Lwjgl3Net();
     92 		this.clipboard = new Lwjgl3Clipboard();
     93 
     94 		Lwjgl3Window window = createWindow(config, listener, 0);
     95 		windows.add(window);
     96 		try {
     97 			loop();
     98 		} catch(Throwable t) {
     99 			if (t instanceof RuntimeException)
    100 				throw (RuntimeException) t;
    101 			else
    102 				throw new GdxRuntimeException(t);
    103 		} finally {
    104 			cleanup();
    105 		}
    106 	}
    107 
    108 	private void loop() {
    109 		Array<Lwjgl3Window> closedWindows = new Array<Lwjgl3Window>();
    110 		while (running && windows.size > 0) {
    111 			// FIXME put it on a separate thread
    112 			if (audio instanceof OpenALAudio) {
    113 				((OpenALAudio) audio).update();
    114 			}
    115 
    116 			closedWindows.clear();
    117 			for (Lwjgl3Window window : windows) {
    118 				Gdx.graphics = window.getGraphics();
    119 				Gdx.gl30 = window.getGraphics().getGL30();
    120 				Gdx.gl20 = Gdx.gl30 != null ? Gdx.gl30 : window.getGraphics().getGL20();
    121 				Gdx.gl = Gdx.gl30 != null ? Gdx.gl30 : Gdx.gl20;
    122 				Gdx.input = window.getInput();
    123 
    124 				GLFW.glfwMakeContextCurrent(window.getWindowHandle());
    125 				currentWindow = window;
    126 				synchronized (lifecycleListeners) {
    127 					window.update(lifecycleListeners);
    128 				}
    129 				if (window.shouldClose()) {
    130 					closedWindows.add(window);
    131 				}
    132 			}
    133 			GLFW.glfwPollEvents();
    134 
    135 			synchronized (runnables) {
    136 				executedRunnables.clear();
    137 				executedRunnables.addAll(runnables);
    138 				runnables.clear();
    139 			}
    140 			for (Runnable runnable : executedRunnables) {
    141 				runnable.run();
    142 			}
    143 
    144 			for (Lwjgl3Window closedWindow : closedWindows) {
    145 				closedWindow.dispose();
    146 				windows.removeValue(closedWindow, false);
    147 			}
    148 		}
    149 	}
    150 
    151 	private void cleanup() {
    152 		for (Lwjgl3Window window : windows) {
    153 			window.dispose();
    154 		}
    155 		Lwjgl3Cursor.disposeSystemCursors();
    156 		if (audio instanceof OpenALAudio) {
    157 			((OpenALAudio) audio).dispose();
    158 		}
    159 		errorCallback.release();
    160 		GLFW.glfwTerminate();
    161 	}
    162 
    163 	@Override
    164 	public ApplicationListener getApplicationListener() {
    165 		return currentWindow.getListener();
    166 	}
    167 
    168 	@Override
    169 	public Graphics getGraphics() {
    170 		return currentWindow.getGraphics();
    171 	}
    172 
    173 	@Override
    174 	public Audio getAudio() {
    175 		return audio;
    176 	}
    177 
    178 	@Override
    179 	public Input getInput() {
    180 		return currentWindow.getInput();
    181 	}
    182 
    183 	@Override
    184 	public Files getFiles() {
    185 		return files;
    186 	}
    187 
    188 	@Override
    189 	public Net getNet() {
    190 		return net;
    191 	}
    192 
    193 	@Override
    194 	public void debug(String tag, String message) {
    195 		if (logLevel >= LOG_DEBUG) {
    196 			System.out.println(tag + ": " + message);
    197 		}
    198 	}
    199 
    200 	@Override
    201 	public void debug(String tag, String message, Throwable exception) {
    202 		if (logLevel >= LOG_DEBUG) {
    203 			System.out.println(tag + ": " + message);
    204 			exception.printStackTrace(System.out);
    205 		}
    206 	}
    207 
    208 	@Override
    209 	public void log(String tag, String message) {
    210 		if (logLevel >= LOG_INFO) {
    211 			System.out.println(tag + ": " + message);
    212 		}
    213 	}
    214 
    215 	@Override
    216 	public void log(String tag, String message, Throwable exception) {
    217 		if (logLevel >= LOG_INFO) {
    218 			System.out.println(tag + ": " + message);
    219 			exception.printStackTrace(System.out);
    220 		}
    221 	}
    222 
    223 	@Override
    224 	public void error(String tag, String message) {
    225 		if (logLevel >= LOG_ERROR) {
    226 			System.err.println(tag + ": " + message);
    227 		}
    228 	}
    229 
    230 	@Override
    231 	public void error(String tag, String message, Throwable exception) {
    232 		if (logLevel >= LOG_ERROR) {
    233 			System.err.println(tag + ": " + message);
    234 			exception.printStackTrace(System.err);
    235 		}
    236 	}
    237 
    238 	@Override
    239 	public void setLogLevel(int logLevel) {
    240 		this.logLevel = logLevel;
    241 	}
    242 
    243 	@Override
    244 	public int getLogLevel() {
    245 		return logLevel;
    246 	}
    247 
    248 	@Override
    249 	public ApplicationType getType() {
    250 		return ApplicationType.Desktop;
    251 	}
    252 
    253 	@Override
    254 	public int getVersion() {
    255 		return 0;
    256 	}
    257 
    258 	@Override
    259 	public long getJavaHeap() {
    260 		return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    261 	}
    262 
    263 	@Override
    264 	public long getNativeHeap() {
    265 		return getJavaHeap();
    266 	}
    267 
    268 	@Override
    269 	public Preferences getPreferences(String name) {
    270 		if (preferences.containsKey(name)) {
    271 			return preferences.get(name);
    272 		} else {
    273 			Preferences prefs = new Lwjgl3Preferences(
    274 					new Lwjgl3FileHandle(new File(config.preferencesDirectory, name), config.preferencesFileType));
    275 			preferences.put(name, prefs);
    276 			return prefs;
    277 		}
    278 	}
    279 
    280 	@Override
    281 	public Clipboard getClipboard() {
    282 		return clipboard;
    283 	}
    284 
    285 	@Override
    286 	public void postRunnable(Runnable runnable) {
    287 		synchronized (runnables) {
    288 			runnables.add(runnable);
    289 		}
    290 	}
    291 
    292 	@Override
    293 	public void exit() {
    294 		running = false;
    295 	}
    296 
    297 	@Override
    298 	public void addLifecycleListener(LifecycleListener listener) {
    299 		synchronized (lifecycleListeners) {
    300 			lifecycleListeners.add(listener);
    301 		}
    302 	}
    303 
    304 	@Override
    305 	public void removeLifecycleListener(LifecycleListener listener) {
    306 		synchronized (lifecycleListeners) {
    307 			lifecycleListeners.add(listener);
    308 		}
    309 	}
    310 
    311 	/**
    312 	 * Creates a new {@link Lwjgl3Window} using the provided listener and {@link Lwjgl3WindowConfiguration}.
    313 	 */
    314 	public Lwjgl3Window newWindow(ApplicationListener listener, Lwjgl3WindowConfiguration config) {
    315 		Lwjgl3ApplicationConfiguration appConfig = Lwjgl3ApplicationConfiguration.copy(this.config);
    316 		appConfig.setWindowedMode(config.windowWidth, config.windowHeight);
    317 		appConfig.setWindowPosition(config.windowX, config.windowY);
    318 		appConfig.setWindowSizeLimits(config.windowMinWidth, config.windowMinHeight, config.windowMaxWidth, config.windowMaxHeight);
    319 		appConfig.setResizable(config.windowResizable);
    320 		appConfig.setDecorated(config.windowDecorated);
    321 		appConfig.setWindowListener(config.windowListener);
    322 		appConfig.setFullscreenMode(config.fullscreenMode);
    323 		appConfig.setTitle(config.title);
    324 		appConfig.setInitialBackgroundColor(config.initialBackgroundColor);
    325 		appConfig.setInitialVisible(config.initialVisible);
    326 		Lwjgl3Window window = createWindow(appConfig, listener, windows.get(0).getWindowHandle());
    327 		windows.add(window);
    328 		return window;
    329 	}
    330 
    331 	private Lwjgl3Window createWindow(Lwjgl3ApplicationConfiguration config, ApplicationListener listener, long sharedContext) {
    332 		long windowHandle = createGlfwWindow(config, sharedContext);
    333 		Lwjgl3Window window = new Lwjgl3Window(windowHandle, listener, config);
    334 		window.setVisible(config.initialVisible);
    335 		return window;
    336 	}
    337 
    338 	static long createGlfwWindow(Lwjgl3ApplicationConfiguration config, long sharedContextWindow) {
    339 		GLFW.glfwDefaultWindowHints();
    340 		GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE);
    341 		GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, config.windowResizable ? GLFW.GLFW_TRUE : GLFW.GLFW_FALSE);
    342 
    343 		if(sharedContextWindow == 0) {
    344 			GLFW.glfwWindowHint(GLFW.GLFW_RED_BITS, config.r);
    345 			GLFW.glfwWindowHint(GLFW.GLFW_GREEN_BITS, config.g);
    346 			GLFW.glfwWindowHint(GLFW.GLFW_BLUE_BITS, config.b);
    347 			GLFW.glfwWindowHint(GLFW.GLFW_ALPHA_BITS, config.a);
    348 			GLFW.glfwWindowHint(GLFW.GLFW_STENCIL_BITS, config.stencil);
    349 			GLFW.glfwWindowHint(GLFW.GLFW_DEPTH_BITS, config.depth);
    350 			GLFW.glfwWindowHint(GLFW.GLFW_SAMPLES, config.samples);
    351 		}
    352 
    353 		if (config.useGL30) {
    354 			GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, config.gles30ContextMajorVersion);
    355 			GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, config.gles30ContextMinorVersion);
    356 			if (SharedLibraryLoader.isMac) {
    357 				// hints mandatory on OS X for GL 3.2+ context creation, but fail on Windows if the
    358 				// WGL_ARB_create_context extension is not available
    359 				// see: http://www.glfw.org/docs/latest/compat.html
    360 				GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GLFW.GLFW_TRUE);
    361 				GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE);
    362 			}
    363 		}
    364 
    365 		long windowHandle = 0;
    366 
    367 		if(config.fullscreenMode != null) {
    368 			// glfwWindowHint(GLFW.GLFW_REFRESH_RATE, config.fullscreenMode.refreshRate);
    369 			windowHandle = GLFW.glfwCreateWindow(config.fullscreenMode.width, config.fullscreenMode.height, config.title, config.fullscreenMode.getMonitor(), sharedContextWindow);
    370 		} else {
    371 			GLFW.glfwWindowHint(GLFW.GLFW_DECORATED, config.windowDecorated? GLFW.GLFW_TRUE: GLFW.GLFW_FALSE);
    372 			windowHandle = GLFW.glfwCreateWindow(config.windowWidth, config.windowHeight, config.title, 0, sharedContextWindow);
    373 		}
    374 		if (windowHandle == 0) {
    375 			throw new GdxRuntimeException("Couldn't create window");
    376 		}
    377 		GLFW.glfwSetWindowSizeLimits(windowHandle,
    378 			config.windowMinWidth > -1 ? config.windowMinWidth : GLFW.GLFW_DONT_CARE,
    379 				config.windowMinHeight > -1 ? config.windowMinHeight : GLFW.GLFW_DONT_CARE,
    380 					config.windowMaxWidth > -1 ? config.windowMaxWidth : GLFW.GLFW_DONT_CARE,
    381 						config.windowMaxHeight> -1 ? config.windowMaxHeight : GLFW.GLFW_DONT_CARE);
    382 		if (config.fullscreenMode == null) {
    383 			if (config.windowX == -1 && config.windowY == -1) {
    384 				int windowWidth = Math.max(config.windowWidth, config.windowMinWidth);
    385 				int windowHeight = Math.max(config.windowHeight, config.windowMinHeight);
    386 				if (config.windowMaxWidth > -1) windowWidth = Math.min(windowWidth, config.windowMaxWidth);
    387 				if (config.windowMaxHeight > -1) windowHeight = Math.min(windowHeight, config.windowMaxHeight);
    388 				GLFWVidMode vidMode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor());
    389 				GLFW.glfwSetWindowPos(windowHandle, vidMode.width() / 2 - windowWidth / 2, vidMode.height() / 2 - windowHeight / 2);
    390 			} else {
    391 				GLFW.glfwSetWindowPos(windowHandle, config.windowX, config.windowY);
    392 			}
    393 		}
    394 		GLFW.glfwMakeContextCurrent(windowHandle);
    395 		GLFW.glfwSwapInterval(config.vSyncEnabled ? 1 : 0);
    396 		GL.createCapabilities();
    397 
    398 		initiateGL();
    399 		if (!glVersion.isVersionEqualToOrHigher(2, 0))
    400 			throw new GdxRuntimeException("OpenGL 2.0 or higher with the FBO extension is required. OpenGL version: "
    401 					+ GL11.glGetString(GL11.GL_VERSION) + "\n" + glVersion.getDebugVersionString());
    402 
    403 		if (!supportsFBO()) {
    404 			throw new GdxRuntimeException("OpenGL 2.0 or higher with the FBO extension is required. OpenGL version: "
    405 					+ GL11.glGetString(GL11.GL_VERSION) + ", FBO extension: false\n" + glVersion.getDebugVersionString());
    406 		}
    407 
    408 		for (int i = 0; i < 2; i++) {
    409 			GL11.glClearColor(config.initialBackgroundColor.r, config.initialBackgroundColor.g, config.initialBackgroundColor.b,
    410 					config.initialBackgroundColor.a);
    411 			GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
    412 			GLFW.glfwSwapBuffers(windowHandle);
    413 		}
    414 		return windowHandle;
    415 	}
    416 
    417 	private static void initiateGL () {
    418 		String versionString = GL11.glGetString(GL11.GL_VERSION);
    419 		String vendorString = GL11.glGetString(GL11.GL_VENDOR);
    420 		String rendererString = GL11.glGetString(GL11.GL_RENDERER);
    421 		glVersion = new GLVersion(Application.ApplicationType.Desktop, versionString, vendorString, rendererString);
    422 	}
    423 
    424 	private static boolean supportsFBO () {
    425 		// FBO is in core since OpenGL 3.0, see https://www.opengl.org/wiki/Framebuffer_Object
    426 		return glVersion.isVersionEqualToOrHigher(3, 0) || GLFW.glfwExtensionSupported("GL_EXT_framebuffer_object") == GLFW.GLFW_TRUE
    427 				|| GLFW.glfwExtensionSupported("GL_ARB_framebuffer_object") == GLFW.GLFW_TRUE;
    428 	}
    429 
    430 }
    431