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