From a0e584f160042d355b51558f3d43f143cb17fc84 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Wed, 19 Jan 2022 20:43:02 +0100 Subject: [PATCH] gdx-lwjgl3-glfw-awt-macos extensions to add support for mixing GLFW and AWT on macOS (#6772) * Apply formatter * Add support for LWJGL3 + AWT on macOS GLFW and AWT both compete for the AppKit run loop. LWJGL3 apps on macOS thus need to specify the -XstartOnFirstThread JVM option. This prevents AWT (and by proxy Swing, ImageIO, etc.) from working together with LWJGL3 on macOS. This commit injects a modified libglfw.dylib (see https://github.com/badlogic/glfw). The GLFW fork is modified such that all calls into AppKit APIs are expected to be performed on a thread != main thread. This way, AWT can take over the run loop, while GLFW (and libGDX LWJGL3) apps can live on a separate thread. To use this, add `gdx-lwjgl3-glfw-awt-macos` as a dependency to your libGDX desktop project. You can now use both libGDX and AWT/Swing/ImageIO on macOS. Note: this extensions does NOT allow rendering your libGDX content to an AWT/Swing window. * Apply formatter * Only initialize AWT if not on EDT - Update Wav and Ogg classes in LWJGL3 backend with latest changes from LWJGL2 backend. - Fix mip map generation when using ANGLE * Resize callback must be called on rendering thread on macOS * Added Lwjgl3Window.flash() * Expose window state * Add awt extension to LWJGL3 tests. * Update libglfw with latest from https://github.com/badlogic/glfw * Pull libglfw.dylib from https://github.com/badlogic/glfw * Fix for #6770, don't apply uberJar task to Android tests. * Updated CHANGES * Removed pom.xml from and fixed Gradle dependency definition in gdx-lwjgl3-glfw-awt-macos, updated CHANGES. * Fixed incorrect PR URL in CHANGES. * Check if shape is not null (#6765) * Add explicit permissions for fix-formatting.yml (#6767) * Add explicit permissions for fix-formatting.yml * Add explicit permissions to build-pullrequest.yml * Add missing method to emulated timer class (#6762) The isEmpty method from the core Timer class was missing in the emulated Timer for the GWT backend. This caused the html build to fail if the isEmpty method was used. * Update Tooltip.java (#6758) * Update Tooltip.java Added option to let user set touch independency for tooltips, which prevents tooltip from showing when targetActor's hitbox is entered with screen already touched * Update Tooltip.java Formatting * Add NWSEResize, NESWResize, AllResize, and NotAllowed SystemCursors (#6756) * Create SystemCursorTest * Add NWSEResize, NESWResize, AllResize, and NotAllowed SystemCursors * Remove dead code in OpenALAudioDevice (#6753) * Add new Pixmap constructor for loading from a ByteBuffer (#6741) * Add new Pixmap constructor for loading from a ByteBuffer * Prevent SIGSEGV if called with a non-direct ByteBuffer. * Updated documentation for getDensity() to warn about potentially costly operation (#6698) Co-authored-by: Johannes Mario Ringheim * Box2D Shape impl disposable (#6528) * Get js heap size on gwt (#6425) * Get js heap size on gwt * Use casting for converting to long * Format * Add missing CHANGES entries [ci skip] Add changelog entries for the notable changes in #6425, #6528, #6758, #6762. * Test for unique name jni issue on ios * Removed unused local. #6753 Co-authored-by: GitHub Action Co-authored-by: Natan <6472084+xpenatan@users.noreply.github.com> Co-authored-by: intrigus Co-authored-by: 6money <47844354+6money@users.noreply.github.com> Co-authored-by: redy5 Co-authored-by: Kyle McLean Co-authored-by: Hangman Co-authored-by: PokeMMO <2398581+PokeMMO@users.noreply.github.com> Co-authored-by: funkyfourier Co-authored-by: Johannes Mario Ringheim Co-authored-by: Fabiitch Co-authored-by: SimonIT Co-authored-by: damios Co-authored-by: Tom Co-authored-by: Nathan Sweet --- .gitignore | 1 + CHANGES | 1 + .../backends/lwjgl3/Lwjgl3Application.java | 16 ++ .../gdx/backends/lwjgl3/Lwjgl3Graphics.java | 36 +++- .../gdx/backends/lwjgl3/Lwjgl3Window.java | 15 ++ .../backends/lwjgl3/audio/OggInputStream.java | 2 +- .../gdx/backends/lwjgl3/audio/Wav.java | 33 +++- build.gradle | 3 +- .../gdx-lwjgl3-glfw-awt-macos/build.gradle | 29 +++ .../backends/lwjgl3/awt/GlfwAWTLoader.java | 185 ++++++++++++++++++ .../gdx/graphics/glutils/MipMapGenerator.java | 4 +- gradle/dist.gradle | 4 +- settings.gradle | 1 + tests/gdx-tests-lwjgl3/build.gradle | 1 + .../gdx/tests/lwjgl3/AwtTestLWJGL.java | 19 +- .../gdx/tests/lwjgl3/Lwjgl3TestStarter.java | 2 - 16 files changed, 319 insertions(+), 33 deletions(-) create mode 100644 extensions/gdx-lwjgl3-glfw-awt-macos/build.gradle create mode 100644 extensions/gdx-lwjgl3-glfw-awt-macos/src/com/badlogic/gdx/backends/lwjgl3/awt/GlfwAWTLoader.java diff --git a/.gitignore b/.gitignore index f69ee16e4d5..6a647141a2f 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,7 @@ libgdx-*.zip.MD5 /extensions/gdx-freetype/libs/ /extensions/gdx-image/libs/ /extensions/gdx-lwjgl3-angle/res +/extensions/gdx-lwjgl3-glfw-awt-macos/res/ /extensions/gdx-setup/gdx-setup.jar /extensions/gdx-setup/test/ diff --git a/CHANGES b/CHANGES index feb4e73158d..049f2ec7c9f 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,7 @@ - [BREAKING CHANGE] Linux: Shared libraries are now built on Ubuntu 18.04 (up from Ubuntu 16.04) - [BREAKING CHANGE] The built-in font files arial-15.fnt and arial-15.png have been replaced with lsans-15.fnt and lsans-15.png; this may change some text layout that uses the built-in font, and code that expects arial-15 assets to be present must change to lsans-15. - [BREAKING CHANGE] Legacy LWJGL3 projects must update the sourceCompatibility to 1.8 or higher. +- LWJGL3 extension: Added gdx-lwjgl3-glfw-awt-macos extension. Fixes GLFW in such a way, that the LWJGL3/libGDX must no longer run on the main thread in macOS, which allows AWT to work in parallel, i.e. file dialogs, JFrames, ImageIO, etc. You no longer need to pass `-XstartOnFirstThread` when starting an LWJGL3 app on macOS. See `AwtTestLWJGL` in gdx-tests-lwjgl3. For more information, see https://github.com/libgdx/libgdx/pull/6772 - API Addition: Added LWJGL3 ANGLE support for x86_64 Windows, Linux, and macOS. Emulates OpenGL ES 2.0 through DirectX (Windows), desktop OpenGL (Linux), and Metal (macOS). May become the preferred method of rendering on macOS if Apple removes OpenGL support entirely. May fix some OpenGL driver issues. More information here: https://github.com/libgdx/libgdx/pull/6672 - iOS: Update to MobiVM 2.3.15 - Update to LWJGL 3.3.0 diff --git a/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Application.java b/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Application.java index 90f590351ad..27abe0c802f 100644 --- a/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Application.java +++ b/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Application.java @@ -57,6 +57,7 @@ import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.SharedLibraryLoader; +import org.lwjgl.system.Configuration; public class Lwjgl3Application implements Lwjgl3ApplicationBase { private final Lwjgl3ApplicationConfiguration config; @@ -80,6 +81,7 @@ public class Lwjgl3Application implements Lwjgl3ApplicationBase { static void initializeGlfw () { if (errorCallback == null) { + if (SharedLibraryLoader.isMac) loadGlfwAwtMacos(); Lwjgl3NativesLoader.load(); errorCallback = GLFWErrorCallback.createPrint(System.err); GLFW.glfwSetErrorCallback(errorCallback); @@ -115,6 +117,20 @@ static void postLoadANGLE () { } } + static void loadGlfwAwtMacos () { + try { + Class loader = Class.forName("com.badlogic.gdx.backends.lwjgl3.awt.GlfwAWTLoader"); + Method load = loader.getMethod("load"); + File sharedLib = (File)load.invoke(loader); + Configuration.GLFW_LIBRARY_NAME.set(sharedLib.getAbsolutePath()); + Configuration.GLFW_CHECK_THREAD0.set(false); + } catch (ClassNotFoundException t) { + return; + } catch (Throwable t) { + throw new GdxRuntimeException("Couldn't load GLFW AWT for macOS.", t); + } + } + public Lwjgl3Application (ApplicationListener listener, Lwjgl3ApplicationConfiguration config) { if (config.glEmulation == Lwjgl3ApplicationConfiguration.GLEmulation.ANGLE_GLES20) loadANGLE(); initializeGlfw(); diff --git a/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Graphics.java b/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Graphics.java index e371046d32c..50654e2bdd9 100644 --- a/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Graphics.java +++ b/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Graphics.java @@ -20,6 +20,8 @@ import com.badlogic.gdx.AbstractGraphics; import com.badlogic.gdx.Application; +import com.badlogic.gdx.Gdx; + import org.lwjgl.BufferUtils; import org.lwjgl.PointerBuffer; import org.lwjgl.glfw.GLFW; @@ -49,6 +51,7 @@ public class Lwjgl3Graphics extends AbstractGraphics implements Disposable { private BufferFormat bufferFormat; private long lastFrameTime = -1; private float deltaTime; + private boolean resetDeltaTime = false; private long frameId; private long frameCounterStart = 0; private int frames; @@ -67,15 +70,20 @@ public class Lwjgl3Graphics extends AbstractGraphics implements Disposable { private GLFWFramebufferSizeCallback resizeCallback = new GLFWFramebufferSizeCallback() { @Override public void invoke (long windowHandle, final int width, final int height) { - updateFramebufferInfo(); - if (!window.isListenerInitialized()) { - return; - } - window.makeCurrent(); - gl20.glViewport(0, 0, width, height); - window.getListener().resize(getWidth(), getHeight()); - window.getListener().render(); - GLFW.glfwSwapBuffers(windowHandle); + Gdx.app.postRunnable(new Runnable() { + @Override + public void run () { + updateFramebufferInfo(); + if (!window.isListenerInitialized()) { + return; + } + window.makeCurrent(); + gl20.glViewport(0, 0, width, height); + window.getListener().resize(getWidth(), getHeight()); + window.getListener().render(); + GLFW.glfwSwapBuffers(windowHandle); + } + }); } }; @@ -139,7 +147,11 @@ void updateFramebufferInfo () { void update () { long time = System.nanoTime(); if (lastFrameTime == -1) lastFrameTime = time; - deltaTime = (time - lastFrameTime) / 1000000000.0f; + if (resetDeltaTime) { + resetDeltaTime = false; + deltaTime = 0; + } else + deltaTime = (time - lastFrameTime) / 1000000000.0f; lastFrameTime = time; if (time - frameCounterStart >= 1000000000) { @@ -222,6 +234,10 @@ public float getDeltaTime () { return deltaTime; } + public void resetDeltaTime () { + resetDeltaTime = true; + } + @Override public int getFramesPerSecond () { return fps; diff --git a/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Window.java b/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Window.java index d685a6a0808..faa55fe8de1 100644 --- a/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Window.java +++ b/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Window.java @@ -51,6 +51,7 @@ public class Lwjgl3Window implements Disposable { private final IntBuffer tmpBuffer; private final IntBuffer tmpBuffer2; boolean iconified = false; + boolean focused = false; private boolean requestRendering = false; private final GLFWWindowFocusCallback focusCallback = new GLFWWindowFocusCallback() { @@ -65,6 +66,7 @@ public void run () { } else { windowListener.focusLost(); } + Lwjgl3Window.this.focused = focused; } } }); @@ -242,6 +244,11 @@ public void iconifyWindow () { GLFW.glfwIconifyWindow(windowHandle); } + /** Whether the window is iconfieid */ + public boolean isIconified () { + return iconified; + } + /** De-minimizes (de-iconifies) and de-maximizes the window. */ public void restoreWindow () { GLFW.glfwRestoreWindow(windowHandle); @@ -257,6 +264,10 @@ public void focusWindow () { GLFW.glfwFocusWindow(windowHandle); } + public boolean isFocused () { + return focused; + } + /** Sets the icon that will be used in the window's title bar. Has no effect in macOS, which doesn't use window icons. * @param image One or more images. The one closest to the system's desired size will be scaled. Good sizes include 16x16, * 32x32 and 48x48. Pixmap format {@link com.badlogic.gdx.graphics.Pixmap.Format#RGBA8888 RGBA8888} is preferred so @@ -456,4 +467,8 @@ public boolean equals (Object obj) { if (windowHandle != other.windowHandle) return false; return true; } + + public void flash () { + GLFW.glfwRequestWindowAttention(windowHandle); + } } diff --git a/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/audio/OggInputStream.java b/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/audio/OggInputStream.java index 8909437112c..fbe5c498edf 100644 --- a/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/audio/OggInputStream.java +++ b/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/audio/OggInputStream.java @@ -105,7 +105,7 @@ public OggInputStream (InputStream input) { * * @param input The input stream from which to read the OGG file * @param previousStream The stream instance to reuse buffers from, may be null */ - OggInputStream (InputStream input, OggInputStream previousStream) { + public OggInputStream (InputStream input, OggInputStream previousStream) { if (previousStream == null) { convbuffer = new byte[convsize]; pcmBuffer = BufferUtils.createByteBuffer(4096 * 500); diff --git a/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/audio/Wav.java b/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/audio/Wav.java index abb00b25384..c16d903b62a 100644 --- a/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/audio/Wav.java +++ b/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/audio/Wav.java @@ -71,10 +71,11 @@ public Sound (OpenALLwjgl3Audio audio, FileHandle file) { } /** @author Nathan Sweet */ - static private class WavInputStream extends FilterInputStream { - int channels, sampleRate, dataRemaining; + static public class WavInputStream extends FilterInputStream { - WavInputStream (FileHandle file) { + public int channels, sampleRate, dataRemaining; + + public WavInputStream (FileHandle file) { super(file.read()); try { if (read() != 'R' || read() != 'I' || read() != 'F' || read() != 'F') @@ -87,8 +88,32 @@ static private class WavInputStream extends FilterInputStream { int fmtChunkLength = seekToChunk('f', 'm', 't', ' '); + // http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html + // http://soundfile.sapp.org/doc/WaveFormat/ int type = read() & 0xff | (read() & 0xff) << 8; - if (type != 1) throw new GdxRuntimeException("WAV files must be PCM: " + type); + if (type != 1) { + String name; + switch (type) { + case 0x0002: + name = "ADPCM"; + break; + case 0x0003: + name = "IEEE float"; + break; + case 0x0006: + name = "8-bit ITU-T G.711 A-law"; + break; + case 0x0007: + name = "8-bit ITU-T G.711 u-law"; + break; + case 0xFFFE: + name = "Extensible"; + break; + default: + name = "Unknown"; + } + throw new GdxRuntimeException("WAV files must be PCM, unsupported format: " + name + " (" + type + ")"); + } channels = read() & 0xff | (read() & 0xff) << 8; if (channels != 1 && channels != 2) throw new GdxRuntimeException("WAV files must have 1 or 2 channels: " + channels); diff --git a/build.gradle b/build.gradle index 8156134094e..ef90a1c5d64 100644 --- a/build.gradle +++ b/build.gradle @@ -168,10 +168,11 @@ task fetchNativesZip() { def zip = file("build/natives.zip"); new URL('https://libgdx-nightlies.s3.eu-central-1.amazonaws.com/libgdx-nightlies/natives.zip').withInputStream{ i -> file("build/natives.zip").withOutputStream{ it << i }} new URL('https://raw.githubusercontent.com/libgdx/gdx-angle-natives/master/gdx-angle-natives.zip').withInputStream{ i -> file("build/gdx-angle-natives.zip").withOutputStream{ it << i }} - + new URL('https://raw.githubusercontent.com/badlogic/glfw/master/libglfw.dylib').withInputStream{ i -> file("extensions/gdx-lwjgl3-glfw-awt-macos/res/macosx64/libglfw.dylib").withOutputStream{ it << i }} } outputs.file(file("build/natives.zip")) outputs.file(file("build/gdx-angle-natives.zip")) + outputs.file(file("extensions/gdx-lwjgl3-glfw-awt-macos/res/macosx64/libglfw.dylib")) } task fetchNativesLwjglAngle(dependsOn: fetchNativesZip, type: Copy) { diff --git a/extensions/gdx-lwjgl3-glfw-awt-macos/build.gradle b/extensions/gdx-lwjgl3-glfw-awt-macos/build.gradle new file mode 100644 index 00000000000..4caa83afa1b --- /dev/null +++ b/extensions/gdx-lwjgl3-glfw-awt-macos/build.gradle @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright 2011 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +dependencies { + implementation project(":gdx") +} + +sourceSets.main.java.srcDirs = ["src"] +sourceSets.main.resources.srcDirs = ["res"] + +// Workaround needed for IDEA to have resources on classpath when running tests (like gdx-tests-lwjgl3) +task copyIdeaResources(type: Copy) { + from "${projectDir}/res" + into "${buildDir}/classes/java/main/" +} +processResources.dependsOn copyIdeaResources \ No newline at end of file diff --git a/extensions/gdx-lwjgl3-glfw-awt-macos/src/com/badlogic/gdx/backends/lwjgl3/awt/GlfwAWTLoader.java b/extensions/gdx-lwjgl3-glfw-awt-macos/src/com/badlogic/gdx/backends/lwjgl3/awt/GlfwAWTLoader.java new file mode 100644 index 00000000000..2421226ba1a --- /dev/null +++ b/extensions/gdx-lwjgl3-glfw-awt-macos/src/com/badlogic/gdx/backends/lwjgl3/awt/GlfwAWTLoader.java @@ -0,0 +1,185 @@ +/******************************************************************************* + * Copyright 2011 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.badlogic.gdx.backends.lwjgl3.awt; + +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.util.Random; +import java.util.UUID; +import java.util.zip.CRC32; + +import com.badlogic.gdx.utils.GdxRuntimeException; + +public class GlfwAWTLoader { + static public boolean isMac = System.getProperty("os.name").contains("Mac"); + + static private final Random random = new Random(); + + public static void closeQuietly (Closeable c) { + if (c != null) { + try { + c.close(); + } catch (Throwable ignored) { + } + } + } + + static String randomUUID () { + return new UUID(random.nextLong(), random.nextLong()).toString(); + } + + public static String crc (InputStream input) { + if (input == null) throw new IllegalArgumentException("input cannot be null."); + CRC32 crc = new CRC32(); + byte[] buffer = new byte[4096]; + try { + while (true) { + int length = input.read(buffer); + if (length == -1) break; + crc.update(buffer, 0, length); + } + } catch (Exception ex) { + } finally { + closeQuietly(input); + } + return Long.toString(crc.getValue(), 16); + } + + private static File extractFile (String sourcePath, File outFile) { + try { + if (!outFile.getParentFile().exists() && !outFile.getParentFile().mkdirs()) throw new GdxRuntimeException( + "Couldn't create ANGLE native library output directory " + outFile.getParentFile().getAbsolutePath()); + OutputStream out = null; + InputStream in = null; + + try { + out = new FileOutputStream(outFile); + in = GlfwAWTLoader.class.getResourceAsStream("/" + sourcePath); + byte[] buffer = new byte[4096]; + while (true) { + int length = in.read(buffer); + if (length == -1) break; + out.write(buffer, 0, length); + } + return outFile; + } finally { + closeQuietly(out); + closeQuietly(in); + } + } catch (Throwable t) { + throw new GdxRuntimeException("Couldn't load ANGLE shared library " + sourcePath, t); + } + } + + /** Returns a path to a file that can be written. Tries multiple locations and verifies writing succeeds. + * @return null if a writable path could not be found. */ + private static File getExtractedFile (String dirName, String fileName) { + // Temp directory with username in path. + File idealFile = new File( + System.getProperty("java.io.tmpdir") + "/libgdx" + System.getProperty("user.name") + "/" + dirName, fileName); + if (canWrite(idealFile)) return idealFile; + + // System provided temp directory. + try { + File file = File.createTempFile(dirName, null); + if (file.delete()) { + file = new File(file, fileName); + if (canWrite(file)) return file; + } + } catch (IOException ignored) { + } + + // User home. + File file = new File(System.getProperty("user.home") + "/.libgdx/" + dirName, fileName); + if (canWrite(file)) return file; + + // Relative directory. + file = new File(".temp/" + dirName, fileName); + if (canWrite(file)) return file; + + // We are running in the OS X sandbox. + if (System.getenv("APP_SANDBOX_CONTAINER_ID") != null) return idealFile; + + return null; + } + + /** Returns true if the parent directories of the file can be created and the file can be written. */ + private static boolean canWrite (File file) { + File parent = file.getParentFile(); + File testFile; + if (file.exists()) { + if (!file.canWrite() || !canExecute(file)) return false; + // Don't overwrite existing file just to check if we can write to directory. + testFile = new File(parent, randomUUID().toString()); + } else { + parent.mkdirs(); + if (!parent.isDirectory()) return false; + testFile = file; + } + try { + new FileOutputStream(testFile).close(); + if (!canExecute(testFile)) return false; + return true; + } catch (Throwable ex) { + return false; + } finally { + testFile.delete(); + } + } + + private static boolean canExecute (File file) { + try { + Method canExecute = File.class.getMethod("canExecute"); + if ((Boolean)canExecute.invoke(file)) return true; + + Method setExecutable = File.class.getMethod("setExecutable", boolean.class, boolean.class); + setExecutable.invoke(file, true, false); + + return (Boolean)canExecute.invoke(file); + } catch (Exception ignored) { + } + return false; + } + + public static File load () { + if (!isMac) return null; + + if (!java.awt.EventQueue.isDispatchThread()) { + try { + java.awt.EventQueue.invokeAndWait(new Runnable() { + public void run () { + java.awt.Toolkit.getDefaultToolkit(); + } + }); + } catch (Throwable t) { + throw new GdxRuntimeException("Couldn't initialize AWT.", t); + } + } + + String source = "macosx64/libglfw.dylib"; + String crc = crc(GlfwAWTLoader.class.getResourceAsStream("/" + source)); + File sharedLib = getExtractedFile(crc, new File(source).getName()); + + extractFile(source, sharedLib); + return sharedLib; + } +} diff --git a/gdx/src/com/badlogic/gdx/graphics/glutils/MipMapGenerator.java b/gdx/src/com/badlogic/gdx/graphics/glutils/MipMapGenerator.java index 51529496883..d1930f135aa 100644 --- a/gdx/src/com/badlogic/gdx/graphics/glutils/MipMapGenerator.java +++ b/gdx/src/com/badlogic/gdx/graphics/glutils/MipMapGenerator.java @@ -67,7 +67,9 @@ private static void generateMipMapGLES20 (int target, Pixmap pixmap) { private static void generateMipMapDesktop (int target, Pixmap pixmap, int textureWidth, int textureHeight) { if (Gdx.graphics.supportsExtension("GL_ARB_framebuffer_object") - || Gdx.graphics.supportsExtension("GL_EXT_framebuffer_object") || Gdx.gl30 != null) { + || Gdx.graphics.supportsExtension("GL_EXT_framebuffer_object") + || Gdx.gl20.getClass().getName().equals("com.badlogic.gdx.backends.lwjgl3.Lwjgl3GLES20") // LWJGL3ANGLE + || Gdx.gl30 != null) { Gdx.gl.glTexImage2D(target, 0, pixmap.getGLInternalFormat(), pixmap.getWidth(), pixmap.getHeight(), 0, pixmap.getGLFormat(), pixmap.getGLType(), pixmap.getPixels()); Gdx.gl20.glGenerateMipmap(target); diff --git a/gradle/dist.gradle b/gradle/dist.gradle index ed176825699..dff9215c181 100644 --- a/gradle/dist.gradle +++ b/gradle/dist.gradle @@ -1,4 +1,4 @@ -allprojects { +configure(subprojects - project(":tests:gdx-tests-android")) { tasks.register('uberJar', Jar) { archiveClassifier = '' archiveVersion = '' @@ -7,7 +7,7 @@ allprojects { from sourceSets.main.output dependsOn configurations.compileClasspath - //dependsOn "printCompileClasspath" + from { configurations.compileClasspath.findAll { it.name.endsWith('jar') && diff --git a/settings.gradle b/settings.gradle index 030ccd0ff6e..38d617bf1db 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,6 +33,7 @@ include ":extensions:gdx-freetype" include ":extensions:gdx-setup" include ":extensions:gdx-tools" include ":extensions:gdx-lwjgl3-angle" +include ":extensions:gdx-lwjgl3-glfw-awt-macos" include ":tests" include ":tests:gdx-tests" diff --git a/tests/gdx-tests-lwjgl3/build.gradle b/tests/gdx-tests-lwjgl3/build.gradle index 21346d65e9e..c934ac29dd0 100644 --- a/tests/gdx-tests-lwjgl3/build.gradle +++ b/tests/gdx-tests-lwjgl3/build.gradle @@ -26,6 +26,7 @@ dependencies { implementation project(":tests:gdx-tests") implementation project(":backends:gdx-backend-lwjgl3") implementation project(":extensions:gdx-lwjgl3-angle") + implementation project(":extensions:gdx-lwjgl3-glfw-awt-macos") implementation testnatives.desktop } diff --git a/tests/gdx-tests-lwjgl3/src/com/badlogic/gdx/tests/lwjgl3/AwtTestLWJGL.java b/tests/gdx-tests-lwjgl3/src/com/badlogic/gdx/tests/lwjgl3/AwtTestLWJGL.java index dde69276241..a0f55eef97f 100644 --- a/tests/gdx-tests-lwjgl3/src/com/badlogic/gdx/tests/lwjgl3/AwtTestLWJGL.java +++ b/tests/gdx-tests-lwjgl3/src/com/badlogic/gdx/tests/lwjgl3/AwtTestLWJGL.java @@ -4,15 +4,17 @@ import java.awt.*; import java.awt.image.BufferedImage; import java.io.IOException; -import java.lang.management.ManagementFactory; import java.net.URL; import javax.imageio.ImageIO; import javax.swing.*; +import com.badlogic.gdx.backends.lwjgl3.awt.GlfwAWTLoader; +import com.badlogic.gdx.utils.SharedLibraryLoader; import org.lwjgl.glfw.GLFWMouseButtonCallback; import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL11; +import org.lwjgl.system.Configuration; import static org.lwjgl.glfw.GLFW.*; @@ -50,17 +52,10 @@ public void run () { }; public static void main (String[] args) throws Exception { - - System.out.println(ManagementFactory.getRuntimeMXBean().getName()); - EventQueue.invokeAndWait(new Runnable() { - public void run () { - Toolkit.getDefaultToolkit(); - new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB); - } - }); - - // Configuration.GLFW_CHECK_THREAD0.set(false); - // Configuration.GLFW_LIBRARY_NAME.set("/Users/badlogic/workspaces/libgdx/glfw/cmake-build-debug/src/libglfw.dylib"); + if (SharedLibraryLoader.isMac) { + Configuration.GLFW_CHECK_THREAD0.set(false); + Configuration.GLFW_LIBRARY_NAME.set(GlfwAWTLoader.load().getAbsolutePath()); + } if (!glfwInit()) { System.out.println("Couldn't initialize GLFW"); diff --git a/tests/gdx-tests-lwjgl3/src/com/badlogic/gdx/tests/lwjgl3/Lwjgl3TestStarter.java b/tests/gdx-tests-lwjgl3/src/com/badlogic/gdx/tests/lwjgl3/Lwjgl3TestStarter.java index d7b9717bda9..08297efecf7 100644 --- a/tests/gdx-tests-lwjgl3/src/com/badlogic/gdx/tests/lwjgl3/Lwjgl3TestStarter.java +++ b/tests/gdx-tests-lwjgl3/src/com/badlogic/gdx/tests/lwjgl3/Lwjgl3TestStarter.java @@ -50,8 +50,6 @@ public class Lwjgl3TestStarter { * * @param argv command line arguments */ public static void main (String[] argv) { - System.setProperty("java.awt.headless", "true"); - options = new CommandLineOptions(argv); Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration();