From 5b3834a46eb57b59e4138b3fcc39a317455e50c0 Mon Sep 17 00:00:00 2001 From: wil Date: Mon, 30 Mar 2026 15:47:27 -0600 Subject: [PATCH 01/14] fix: backend improvement | remove | add --- .../com/jme3/system/lwjgl/LwjglCanvas.java | 518 ++++++++++++------ .../com/jme3/system/lwjgl/LwjglWindow.java | 8 +- .../lwjglx/LwjglxDefaultGLPlatform.java | 13 +- .../com/jme3/system/lwjglx/X11GLPlatform.java | 63 --- 4 files changed, 354 insertions(+), 248 deletions(-) diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index 6fa525f355..eb4e7270ab 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -31,16 +31,21 @@ */ package com.jme3.system.lwjgl; +import com.jme3.input.JoyInput; import com.jme3.input.KeyInput; import com.jme3.input.MouseInput; +import com.jme3.input.TouchInput; import com.jme3.input.awt.AwtKeyInput; import com.jme3.input.awt.AwtMouseInput; +import com.jme3.input.lwjgl.SdlJoystickInput; import com.jme3.system.AppSettings; +import com.jme3.system.Displays; import com.jme3.system.JmeCanvasContext; import com.jme3.system.lwjglx.LwjglxGLPlatform; import java.awt.AWTException; import java.awt.Canvas; +import java.awt.Component; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; @@ -52,18 +57,19 @@ import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; +import org.lwjgl.Version; import org.lwjgl.awthacks.NonClearGraphics; import org.lwjgl.awthacks.NonClearGraphics2D; import org.lwjgl.opengl.awt.GLData; -import org.lwjgl.system.Platform; +import javax.swing.*; + +import org.lwjgl.system.Configuration; import static org.lwjgl.system.MemoryUtil.*; import static com.jme3.system.lwjglx.LwjglxDefaultGLPlatform.*; @@ -103,9 +109,6 @@ public class LwjglCanvas extends LwjglWindow implements JmeCanvasContext, Runnab /** GL versions map. */ private static final Map> RENDER_CONFIGS = new HashMap<>(); - /** Type of operating system where this context is running. */ - private static final Platform OS = Platform.get(); - /* Register the different versions. */ @@ -210,6 +213,46 @@ public synchronized void addComponentListener(ComponentListener l) { super.addComponentListener(l); } + /** + * This is where the OpenGL rendering context is generated. + */ + public void createContext() { + try { + context = platformCanvas.create(this, data, effective); + } catch (AWTException e) { + listener.handleError("Exception while creating the OpenGL context", e); + } + } + + public boolean hasContext() { + synchronized (lock) { + return context != NULL; + } + } + + /** + * Make the canvas' context current. It is highly recommended that the + * context is only made current inside the AWT thread (for example in an + * overridden paintGL()). + */ + public void makeCurrent() { + synchronized (lock) { + if (context == NULL) { + throw new IllegalStateException("Canvas not yet displayable"); + } + platformCanvas.makeCurrent(context); + } + } + + /** + * Release the rendering context + */ + public void releaseContext() { + synchronized (lock) { + platformCanvas.makeCurrent(NULL); + } + } + /** * Returns the effective data (recommended or ideal) to initialize the * LWJGL3-AWT context. @@ -221,61 +264,37 @@ public GLData getGLDataEffective() { } /** - * Called after beforeRender() to release the threads (unlock); - * so that AWT can update its threads normally. - *

- * NOTE: It is very important to call this method and not leave AWT - * hanging (breaking it) regardless of whether an error occurs during OpenGL - * rendering. + * To start drawing on the AWT surface, the AWT threads must be locked to + * avoid conflicts when drawing on the canvas. */ - public void afterRender() { - // release the rendering context - platformCanvas.makeCurrent(NULL); - try { - platformCanvas.unlock(); // <- MUST unlock on Linux - } catch (AWTException e) { - listener.handleError("Failed to unlock Canvas", e); + public void lock() { + synchronized (lock) { + try { + platformCanvas.lock();// <- MUST lock on Linux + } catch (AWTException e) { + listener.handleError("Failed to lock Canvas", e); + } } } /** - * Called before afterRender() to prepare the AWT drawing surface. + * Unlock the current AWT thread to continue updating the user interface. */ - public void beforeRender() { - // this is where the OpenGL rendering context is generated. - if (context == NULL) { + public void unlock() { + synchronized (lock) { try { - context = platformCanvas.create(this, data, effective); + platformCanvas.unlock();// <- MUST unlock on Linux } catch (AWTException e) { - listener.handleError("Exception while creating the OpenGL context", e); - return; + listener.handleError("Failed to unlock Canvas", e); } } - - /* - * To start drawing on the AWT surface, the AWT threads must be locked to - * avoid conflicts when drawing on the canvas. - */ - try { - platformCanvas.lock(); // <- MUST lock on Linux - } catch (AWTException e) { - listener.handleError("Failed to lock Canvas", e); - } - - /* - * The 'makeCurrent(long)' method converts the specified OpenGL rendering - * context to the current rendering context. - */ - platformCanvas.makeCurrent(context); } /** - * Frees up the drawing surface (only on Windows and MacOSX). + * Frees up the drawing surface */ public void doDisposeCanvas() { - if (OS != Platform.LINUX) { - platformCanvas.dispose(); - } + platformCanvas.dispose(); } /** @@ -305,18 +324,33 @@ public void addNotify() { */ @Override public void removeNotify() { + if (needClose.get()) { + LOGGER.log(Level.FINE, "EDT: Application is stopped. Not restoring canvas."); + super.removeNotify(); + return; + } + synchronized (lock) { // prepare for a possible re-adding - if ((OS != Platform.LINUX) && (context != NULL)) { - platformCanvas.deleteContext(context); - context = NULL; - } hasNativePeer.set(false); - } - super.removeNotify(); - if (OS == Platform.WINDOWS) { - LOGGER.log(Level.WARNING, "Windows does not support this functionality: remove(__canvas__)"); + reinitcontext.set(true); + + while (reinitcontext.get()) { + try { + lock.wait(); + } catch (InterruptedException ex) { + super.removeNotify(); + return; + } + } + + reinitcontext.set(false); } + + // GL context is dead at this point + LOGGER.log(Level.FINE, "EDT: Acknowledged receipt of canvas death"); + System.out.println(""); + super.removeNotify(); } /** @@ -327,6 +361,10 @@ public void destroy() { platformCanvas.destroy(); } + public void deleteContext() { + platformCanvas.deleteContext(context); + } + /** * Returns Graphics object that ignores {@link java.awt.Graphics#clearRect(int, int, int, int)} * calls. @@ -356,14 +394,12 @@ public Graphics getGraphics() { */ private GLData glData; - /** Used to display the effective data for the {@code AWT-Swing} drawing surface per console. */ - private final AtomicBoolean showGLDataEffective = new AtomicBoolean(false); - /** Used to notify the canvas status ({@code remove()/add()}). */ private final AtomicBoolean hasNativePeer = new AtomicBoolean(false); + /** It is used to create the initial context and all the resources that will be activated only once. */ + private final AtomicBoolean initialize = new AtomicBoolean(false); - /** Notify if the canvas is visible and has a parent.*/ - private final AtomicBoolean showing = new AtomicBoolean(false); + private final AtomicBoolean reinitcontext = new AtomicBoolean(false); /** Notify if there is a change in canvas dimensions. */ private AtomicBoolean needResize = new AtomicBoolean(false); @@ -373,10 +409,7 @@ public Graphics getGraphics() { * it from being initialized multiple times and potentially breaking the JVM. */ private AtomicBoolean contextFlag = new AtomicBoolean(false); - - /** Semaphort used to check the "terminate" signal. */ - private final Semaphore signalTerminate = new Semaphore(0); - + /** lock-object. */ private final Object lock = new Object(); @@ -429,6 +462,153 @@ public void componentResized(ComponentEvent e) { } }); } + + /** + * Check if the canvas is displayed, that is, if it has a parent that has set it up. + *

+ * It is very important that this verification be done so that LWJGL3-AWT works correctly. + * + * @return returns true if the canvas is ready to draw; otherwise + * returns false + */ + public boolean checkVisibilityState() { + if (!hasNativePeer.get()) { + return false; + } + return canvas.isDisplayable() && canvas.isShowing(); + } + + /** + * Here the entire GL context is rendered and initialized. + */ + @Override + public void run() { + if (listener == null) { + throw new IllegalStateException( + "SystemListener is not set on context!" + "Must set with JmeContext.setSystemListener()." + ); + } + + LOGGER.log(Level.FINE, "Using LWJGL {0}", Version.getVersion()); + + while (true) { + if (needResize.getAndSet(false)) { + settings.setResolution(framebufferWidth, framebufferHeight); + listener.reshape(framebufferWidth, framebufferHeight); + } + + synchronized (lock) { + if (reinitcontext.getAndSet(false)) { + LOGGER.log(Level.FINE, "LWJGX: Destroying display .."); + + listener.loseFocus(); + renderer.invalidateState(); + renderer.cleanup(); + + canvas.releaseContext(); + canvas.deleteContext(); + canvas.context = NULL; + + renderable.set(false); + lock.notifyAll(); + } + } + + if (checkVisibilityState()) { + // HACK: All components of the thread hosted in initInt() must be + // called after the context is created, but this is only valid + // if the canvas is validated by AWT, so it is created at "runtime". + if (!initialize.getAndSet(true)) { + if (!initInThread()) { + LOGGER.log(Level.SEVERE, "Display initialization failed. Cannot continue."); + break; + } + } else { + if (!canvas.hasContext()) { + LOGGER.log(Level.FINE, "OGL: Creating display .."); + createContext(settings); + reinitContext(); + + renderer.cleanup(); + renderer.invalidateState(); + listener.gainFocus(); + } + } + + // HACK: In this thread, let OpenGL handle the heavy lifting, + // blocking the AWT/Swing EDT only to draw the corresponding + // buffer, thus preventing the user interface from freezing + // with demanding scenes. + runLoop(); + + // All this does is call swapBuffers(). + // If the canvas is not active, there's no need to waste time + // doing that. + if (renderable.get() && canvas.hasContext()) { + try { + if (allowSwapBuffers && autoFlush) { + // calls swap buffers | lock, etc. + try { + canvas.lock(); + canvas.swapBuffers(); + } finally { + canvas.unlock(); + } + + // Sync the display on some systems. + Toolkit.getDefaultToolkit().sync(); + } + } catch (Throwable ex) { + listener.handleError("Error while swapping buffers", ex); + } + } + } else { + //canvas.doDisposeCanvas(); + try { + Thread.sleep(16); + } catch (InterruptedException ignore) { } + } + + if (needClose.get()) { + break; + } + } + + deinitInThread(); + } + + /** + * execute one iteration of the render loop in the OpenGL thread + */ + @Override + protected void runLoop() { + // If a restart is required, lets recreate the context. + if (needRestart.getAndSet(false)) { + restartContext(); + } + + if (!created.get()) { + throw new IllegalStateException(); + } + + listener.update(); + + // Subclasses just call GLObjectManager. Clean up objects here. + // It is safe ... for now. + if (renderer != null) { + renderer.postFrame(); + } + + if (autoFlush) { + if (frameRateLimit != getSettings().getFrameRate()) { + setFrameRateLimit(getSettings().getFrameRate()); + } + } else if (frameRateLimit != 20) { + setFrameRateLimit(20); + } + + Sync.sync(frameRateLimit); + } /** * (non-Javadoc) @@ -439,6 +619,7 @@ public void componentResized(ComponentEvent e) { public void destroy(boolean waitFor) { super.destroy(waitFor); this.contextFlag.set(false); + this.initialize.set(false); } /** @@ -456,6 +637,18 @@ public void create(boolean waitFor) { this.contextFlag.set(true); } + /** + * (non-Javadoc) + * @see com.jme3.system.lwjgl.LwjglWindow#destroyContext() + */ + @Override + protected void destroyContext() { + synchronized (lock) { + canvas.destroy(); + } + super.destroyContext(); + } + /** * (non-Javadoc) * @see com.jme3.system.lwjgl.LwjglWindow#createContext(com.jme3.system.AppSettings) @@ -463,13 +656,17 @@ public void create(boolean waitFor) { */ @Override protected void createContext(AppSettings settings) { - try { - Thread.sleep(1000); - } catch (InterruptedException ex) { - LOGGER.log(Level.SEVERE, "LWJGL3-AWT: Interrupted!", ex); + if (!settings.isX11PlatformPreferred() && isWayland()) { + LOGGER.log(Level.WARNING, "LWJGLX and AWT/Swing only work with X11, so XWayland will be used for GLX."); + } + + // HACK: For LWJGLX to work in Wyland, it is necessary to use GLX via + // XWayland, so LWJGL must be forced to load GLX as a native API. + // This is because LWJGLX does not provide an EGL context. + if (isWayland()) { + Configuration.OPENGL_CONTEXT_API.set("native"); } - super.createContext(settings); RENDER_CONFIGS.computeIfAbsent(settings.getRenderer(), (t) -> { return (data) -> { data.majorVersion = 2; @@ -494,9 +691,6 @@ protected void createContext(AppSettings settings) { glData.swapInterval = 0; } - // This will activate the "effective data" scrubber. - showGLDataEffective.set(settings.getBoolean("GLDataEffectiveDebug")); - glData.alphaSize = settings.getAlphaBits(); glData.sRGB = settings.isGammaCorrection(); // Not compatible with very old devices @@ -507,31 +701,23 @@ protected void createContext(AppSettings settings) { glData.debug = settings.isGraphicsDebug(); glData.api = GLData.API.GL; + + allowSwapBuffers = settings.isSwapBuffers(); + + canvas.createContext(); + canvas.makeCurrent(); + + // This will activate the "effective data" scrubber. + if (settings.getBoolean("GLDataEffectiveDebug")) { + System.out.println(MessageFormat.format("[ DEBUGGER ] :Effective data to initialize the LWJGL3-AWT context\n{0}", + getPrintContextInitInfo(canvas.getGLDataEffective()))); + } } - - /** - * Returns the AWT component where it is drawn (canvas). - * @return Canvas - */ - @Override - public Canvas getCanvas() { - return canvas; - } - - /** (non-Javadoc) */ - @Override - protected void showWindow() { } - /** (non-Javadoc) */ - @Override - protected void setWindowIcon(final AppSettings settings) { } - /** (non-Javadoc) */ - @Override - public void setTitle(String title) { } /** * (non-Javadoc) * @see com.jme3.system.lwjgl.LwjglWindow#getKeyInput() - * @return returns a {@link com.jme3.input.awt.AwtKeyInput} object + * @return KeyInput */ @Override public KeyInput getKeyInput() { @@ -545,7 +731,7 @@ public KeyInput getKeyInput() { /** * (non-Javadoc) * @see com.jme3.system.lwjgl.LwjglWindow#getMouseInput() - * @return returns a {@link com.jme3.input.awt.AwtMouseInput} object + * @return MouseInput */ @Override public MouseInput getMouseInput() { @@ -556,102 +742,37 @@ public MouseInput getMouseInput() { return mouseInput; } - /** - * Check if the canvas is displayed, that is, if it has a parent that has set it up. - *

- * It is very important that this verification be done so that LWJGL3-AWT works correctly. - * - * @return returns true if the canvas is ready to draw; otherwise - * returns false - */ - public boolean checkVisibilityState() { - if (!hasNativePeer.get()) { - synchronized (lock) { - canvas.doDisposeCanvas(); - } - return false; - } - - boolean currentShowing = canvas.isShowing(); - showing.set(currentShowing); - return currentShowing; - } - /** * (non-Javadoc) - * @see com.jme3.system.lwjgl.LwjglWindow#destroyContext() + * @see com.jme3.system.lwjgl.LwjglWindow#getJoyInput() + * @return JoyInput */ @Override - protected void destroyContext() { - synchronized (lock) { - canvas.destroy(); + public JoyInput getJoyInput() { + if (joyInput == null) { + String mapper = settings.getJoysticksMapper(); + if (AppSettings.JOYSTICKS_LEGACY_MAPPER.equals(mapper) + || AppSettings.JOYSTICKS_XBOX_LEGACY_MAPPER.equals(mapper)) { + + LOGGER.log(Level.WARNING, () -> "LWJGX does not support this configuration: " + mapper); + } + joyInput = new SdlJoystickInput(settings); } - - // request the cleanup - signalTerminate.release(); - super.destroyContext(); + return joyInput; } - /** - * (non-Javadoc) - * @see com.jme3.system.lwjgl.LwjglWindow#runLoop() - */ + /** (non-Javadoc) */ @Override - protected void runLoop() { - if (needResize.get()) { - needResize.set(false); - settings.setResolution(framebufferWidth, framebufferHeight); - listener.reshape(framebufferWidth, framebufferHeight); - } - - // check component status - if (!checkVisibilityState()) { - return; - } - - //---------------------------------------------------------------------- - // AWT - RENDERER - //---------------------------------------------------------------------- - /* - * The same logic as AWTGLCanvas is used to draw on the awt drawing surface: - * - * 1. Lock any thread to avoid any conflict. - * 2. Buffer swap (this is where the framebuffer is actually drawn): swapBuffers() - * 3. Unlock so that the AWT thread can work normally. IF NOT DONE, IT WILL - * BE WAITING AND BREAK ANY AWT/Swing APP. - */ - canvas.beforeRender(); - try { - super.runLoop(); - if (allowSwapBuffers && autoFlush) { - canvas.swapBuffers(); - } - } finally { - canvas.afterRender(); - } - - // Sync the display on some systems. - Toolkit.getDefaultToolkit().sync(); - - //---------------------------------------------------------------------- - /* - * Whether it is necessary to know the effective attributes to - * initialize the LWJGL3-AWT context - */ - //---------------------------------------------------------------------- - if (showGLDataEffective.get()) { - showGLDataEffective.set(false); - System.out.println(MessageFormat.format("[ DEBUGGER ] :Effective data to initialize the LWJGL3-AWT context\n{0}", - getPrintContextInitInfo(canvas.getGLDataEffective()))); - } + public TouchInput getTouchInput() { return null; } + /** (non-Javadoc) */ + @Override protected void updateSizes() { } + /** (non-Javadoc) */ + @Override protected void showWindow() { } + /** (non-Javadoc) */ + @Override protected void setWindowIcon(final AppSettings settings) { } + /** (non-Javadoc) */ + @Override public void setTitle(String title) { } - try { - if (signalTerminate.tryAcquire(10, TimeUnit.MILLISECONDS)) { - canvas.doDisposeCanvas(); - } - } catch (InterruptedException ignored) { } - } - /** * (non-Javadoc) * @see com.jme3.system.lwjgl.LwjglContext#printContextInitInfo() @@ -752,4 +873,41 @@ public int getFramebufferHeight() { public int getFramebufferWidth() { return this.framebufferWidth; } -} + + @Override + public int getWindowXPosition() { + Component component = SwingUtilities.getRoot(canvas); + if (component == null) { + return 0; + } + return component.getX(); + } + + @Override + public int getWindowYPosition() { + Component component = SwingUtilities.getRoot(canvas); + if (component == null) { + return 0; + } + return component.getY(); + } + + @Override + public Displays getDisplays() { + return null; + } + + @Override + public int getPrimaryDisplay() { + return 0; + } + + /** + * Returns the AWT component where it is drawn (canvas). + * @return Canvas + */ + @Override + public Canvas getCanvas() { + return canvas; + } +} \ No newline at end of file diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java index 3e7ed08744..668739f6f8 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java @@ -183,7 +183,7 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { private long monitor = NULL; private long window = NULL; - private int frameRateLimit = -1; + protected int frameRateLimit = -1; protected boolean wasActive = false; protected boolean autoFlush = true; @@ -478,7 +478,7 @@ public void invoke(final long window, final int width, final int height) { updateSizes(); } - private void updateSizes() { + protected void updateSizes() { // framebuffer size (resolution) may differ from window size (e.g. HiDPI) glfwGetWindowSize(window, width, height); @@ -762,7 +762,7 @@ protected void runLoop() { glfwPollEvents(); } - private void restartContext() { + protected void restartContext() { try { destroyContext(); createContext(settings); @@ -783,7 +783,7 @@ private void restartContext() { LOGGER.fine("Display restarted."); } - private void setFrameRateLimit(int frameRateLimit) { + protected final void setFrameRateLimit(int frameRateLimit) { this.frameRateLimit = frameRateLimit; } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxDefaultGLPlatform.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxDefaultGLPlatform.java index 3f7be3bcf3..fc0f5777b5 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxDefaultGLPlatform.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxDefaultGLPlatform.java @@ -40,6 +40,17 @@ */ public final class LwjglxDefaultGLPlatform { + public static boolean isWayland() { + Platform platform = Platform.get(); + if (platform == LINUX || platform == FREEBSD) { + // The following matches the test GLFW does to enable the Wayland backend. + if ("wayland".equals(System.getenv("XDG_SESSION_TYPE")) && System.getenv("WAYLAND_DISPLAY") != null) { + return true; + } + } + return false; + } + /** * Returns a drawing platform based on the platform it is running on. * @return LwjglxGLPlatform @@ -49,7 +60,7 @@ public static LwjglxGLPlatform createLwjglxGLPlatform() throws UnsupportedOperat switch (Platform.get()) { case WINDOWS: return new Win32GLPlatform(); - //case FREEBSD: -> In future versions of lwjgl3 (possibly) + case FREEBSD: case LINUX: return new X11GLPlatform(); case MACOSX: diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java index 198b9b7317..1bd0da34c5 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java @@ -32,9 +32,6 @@ package com.jme3.system.lwjglx; import org.lwjgl.opengl.awt.PlatformLinuxGLCanvas; -import org.lwjgl.system.jawt.*; - -import static org.lwjgl.system.MemoryUtil.*; import static org.lwjgl.system.jawt.JAWTFunctions.*; /** @@ -49,66 +46,6 @@ */ final class X11GLPlatform extends PlatformLinuxGLCanvas implements LwjglxGLPlatform { - /** - * (non-Javadoc) - * @see org.lwjgl.opengl.awt.PlatformGLCanvas#swapBuffers() - * @return boolean - */ - @Override - public boolean swapBuffers() { - // Get the drawing surface info - JAWTDrawingSurfaceInfo dsi = JAWT_DrawingSurface_GetDrawingSurfaceInfo(ds, ds.GetDrawingSurfaceInfo()); - if (dsi == null) { - throw new IllegalStateException("JAWT_DrawingSurface_GetDrawingSurfaceInfo() failed"); - } - - try { - // Get the platform-specific drawing info - JAWTX11DrawingSurfaceInfo dsi_x11 = JAWTX11DrawingSurfaceInfo.create(dsi.platformInfo()); - - // Set new values - display = dsi_x11.display(); - drawable = dsi_x11.drawable(); - - // Swap-Buffers - return super.swapBuffers(); - } finally { - JAWT_DrawingSurface_FreeDrawingSurfaceInfo(dsi, ds.FreeDrawingSurfaceInfo()); - } - } - - /** - * (non-Javadoc) - * @see org.lwjgl.opengl.awt.PlatformGLCanvas#makeCurrent(long) - * - * @param context long - * @return boolean - */ - @Override - public boolean makeCurrent(long context) { - // Get the drawing surface info - JAWTDrawingSurfaceInfo dsi = JAWT_DrawingSurface_GetDrawingSurfaceInfo(ds, ds.GetDrawingSurfaceInfo()); - if (dsi == null) { - throw new IllegalStateException("JAWT_DrawingSurface_GetDrawingSurfaceInfo() failed"); - } - - try { - // Get the platform-specific drawing info - JAWTX11DrawingSurfaceInfo dsi_x11 = JAWTX11DrawingSurfaceInfo.create(dsi.platformInfo()); - - // Set new values - display = dsi_x11.display(); - drawable = dsi_x11.drawable(); - - if (drawable == NULL) { - return false; - } - return super.makeCurrent(context); - } finally { - JAWT_DrawingSurface_FreeDrawingSurfaceInfo(dsi, ds.FreeDrawingSurfaceInfo()); - } - } - /** * (non-Javadoc) * @see com.jme3.system.lwjglx.LwjglxGLPlatform#destroy() From 5db9ce451f3afaf0b4e73ad4f9232df702b49cab Mon Sep 17 00:00:00 2001 From: wil Date: Mon, 30 Mar 2026 16:10:48 -0600 Subject: [PATCH 02/14] docs: javadoc --- .../com/jme3/system/lwjgl/LwjglCanvas.java | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index eb4e7270ab..0d84b4f2f2 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -223,7 +223,7 @@ public void createContext() { listener.handleError("Exception while creating the OpenGL context", e); } } - + /** (non-Javadoc) */ public boolean hasContext() { synchronized (lock) { return context != NULL; @@ -360,7 +360,7 @@ public void removeNotify() { public void destroy() { platformCanvas.destroy(); } - + /** (non-Javadoc) */ public void deleteContext() { platformCanvas.deleteContext(context); } @@ -396,9 +396,12 @@ public Graphics getGraphics() { /** Used to notify the canvas status ({@code remove()/add()}). */ private final AtomicBoolean hasNativePeer = new AtomicBoolean(false); - /** It is used to create the initial context and all the resources that will be activated only once. */ + /** + * It is used to create the initial context and all the resources that will + * be activated only once. + */ private final AtomicBoolean initialize = new AtomicBoolean(false); - + /** Notify the context reintegration, invalidating the current renderer. */ private final AtomicBoolean reinitcontext = new AtomicBoolean(false); /** Notify if there is a change in canvas dimensions. */ @@ -762,8 +765,7 @@ public JoyInput getJoyInput() { } /** (non-Javadoc) */ - @Override - public TouchInput getTouchInput() { return null; } + @Override public TouchInput getTouchInput() { return null; } /** (non-Javadoc) */ @Override protected void updateSizes() { } /** (non-Javadoc) */ @@ -874,6 +876,7 @@ public int getFramebufferWidth() { return this.framebufferWidth; } + /** (non-Javadoc) */ @Override public int getWindowXPosition() { Component component = SwingUtilities.getRoot(canvas); @@ -883,6 +886,7 @@ public int getWindowXPosition() { return component.getX(); } + /** (non-Javadoc) */ @Override public int getWindowYPosition() { Component component = SwingUtilities.getRoot(canvas); @@ -892,15 +896,10 @@ public int getWindowYPosition() { return component.getY(); } - @Override - public Displays getDisplays() { - return null; - } - - @Override - public int getPrimaryDisplay() { - return 0; - } + /** (non-Javadoc) */ + @Override public Displays getDisplays() { return null; } + /** (non-Javadoc) */ + @Override public int getPrimaryDisplay() { return 0; } /** * Returns the AWT component where it is drawn (canvas). From 72ccec5fd930a60a76624c656092c134d505a30d Mon Sep 17 00:00:00 2001 From: wil Date: Mon, 30 Mar 2026 16:24:06 -0600 Subject: [PATCH 03/14] docs: javadoc --- .../src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index 0d84b4f2f2..b7ca787bfe 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -349,7 +349,6 @@ public void removeNotify() { // GL context is dead at this point LOGGER.log(Level.FINE, "EDT: Acknowledged receipt of canvas death"); - System.out.println(""); super.removeNotify(); } @@ -715,6 +714,10 @@ protected void createContext(AppSettings settings) { System.out.println(MessageFormat.format("[ DEBUGGER ] :Effective data to initialize the LWJGL3-AWT context\n{0}", getPrintContextInitInfo(canvas.getGLDataEffective()))); } + // Create OpenCL + if (settings.isOpenCLSupport()) { + + } } /** From f9b7140d042fce94c36e54145f8767e6e7b55524 Mon Sep 17 00:00:00 2001 From: wil Date: Mon, 30 Mar 2026 17:08:54 -0600 Subject: [PATCH 04/14] fix: reinit --- .../src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index b7ca787bfe..64cbf3ca18 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -531,8 +531,6 @@ public void run() { createContext(settings); reinitContext(); - renderer.cleanup(); - renderer.invalidateState(); listener.gainFocus(); } } From 84c1360c936747f771f6aabcd4636a7c811abcba Mon Sep 17 00:00:00 2001 From: wil Date: Mon, 30 Mar 2026 17:23:15 -0600 Subject: [PATCH 05/14] fix: possible exception | NullPointerException --- .../src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index 64cbf3ca18..f7c1aeb904 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -504,8 +504,10 @@ public void run() { LOGGER.log(Level.FINE, "LWJGX: Destroying display .."); listener.loseFocus(); - renderer.invalidateState(); - renderer.cleanup(); + if (renderer != null) { + renderer.invalidateState(); + renderer.cleanup(); + } canvas.releaseContext(); canvas.deleteContext(); From f88ae1e4d47930bf5ae1fc8f040b3baea350c6b3 Mon Sep 17 00:00:00 2001 From: wil Date: Mon, 30 Mar 2026 17:29:46 -0600 Subject: [PATCH 06/14] fix: logger --- .../src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index f7c1aeb904..62ea96e20b 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -711,8 +711,8 @@ protected void createContext(AppSettings settings) { // This will activate the "effective data" scrubber. if (settings.getBoolean("GLDataEffectiveDebug")) { - System.out.println(MessageFormat.format("[ DEBUGGER ] :Effective data to initialize the LWJGL3-AWT context\n{0}", - getPrintContextInitInfo(canvas.getGLDataEffective()))); + LOGGER.log(Level.INFO, "[ DEBUGGER ] :Effective data to initialize the LWJGL3-AWT context\n{0}", + getPrintContextInitInfo(canvas.getGLDataEffective())); } // Create OpenCL if (settings.isOpenCLSupport()) { From 1a09be9c9a5d175ae977e4d4426bafb8228ab0de Mon Sep 17 00:00:00 2001 From: wil Date: Mon, 30 Mar 2026 17:36:27 -0600 Subject: [PATCH 07/14] fix: disponse --- .../src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index 62ea96e20b..5c28ca5a55 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -565,7 +565,7 @@ public void run() { } } } else { - //canvas.doDisposeCanvas(); + canvas.doDisposeCanvas(); try { Thread.sleep(16); } catch (InterruptedException ignore) { } From ef25bcb3eea759a6d493ec9c1b9eb17d21c445f2 Mon Sep 17 00:00:00 2001 From: wil Date: Tue, 31 Mar 2026 12:06:30 -0600 Subject: [PATCH 08/14] feat: OpenCL support --- .../com/jme3/system/lwjgl/LwjglCanvas.java | 39 +++++++++++++-- .../com/jme3/system/lwjgl/LwjglContext.java | 49 ++++++++++--------- .../com/jme3/system/lwjgl/LwjglWindow.java | 2 +- .../lwjglx/LwjglxDefaultGLPlatform.java | 29 ++++++++++- .../com/jme3/system/lwjglx/X11GLPlatform.java | 4 ++ 5 files changed, 93 insertions(+), 30 deletions(-) diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index 5c28ca5a55..bcc9270805 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2025 jMonkeyEngine + * Copyright (c) 2009-2026 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -54,7 +54,9 @@ import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.geom.AffineTransform; -import java.text.MessageFormat; + +import javax.swing.SwingUtilities; + import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; @@ -67,9 +69,13 @@ import org.lwjgl.awthacks.NonClearGraphics2D; import org.lwjgl.opengl.awt.GLData; -import javax.swing.*; - import org.lwjgl.system.Configuration; +import org.lwjgl.opencl.APPLEGLSharing; +import org.lwjgl.opencl.KHRGLSharing; +import org.lwjgl.opengl.CGL; +import org.lwjgl.opengl.WGL; +import org.lwjgl.system.Platform; + import static org.lwjgl.system.MemoryUtil.*; import static com.jme3.system.lwjglx.LwjglxDefaultGLPlatform.*; @@ -716,7 +722,30 @@ protected void createContext(AppSettings settings) { } // Create OpenCL if (settings.isOpenCLSupport()) { - + initOpenCL(canvas.context, (properties, context) -> { + switch (Platform.get()) { + case WINDOWS: + properties.put(KHRGLSharing.CL_GL_CONTEXT_KHR) + .put(context) + .put(KHRGLSharing.CL_WGL_HDC_KHR) + .put(WGL.wglGetCurrentDC()); + break; + case FREEBSD: + case LINUX: + properties.put(KHRGLSharing.CL_GL_CONTEXT_KHR) + .put(context) + .put(KHRGLSharing.CL_GLX_DISPLAY_KHR) + .put(getX11Display(canvas.platformCanvas)); + break; + case MACOSX: + properties.put(APPLEGLSharing.CL_CONTEXT_PROPERTY_USE_CGL_SHAREGROUP_APPLE) + .put(CGL.CGLGetShareGroup(CGL.CGLGetCurrentContext())); + break; + default: + break; // Unknown Platform, do nothing. + } + return null; + }); } } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java index 535fd295b1..6500c3c96c 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java @@ -62,6 +62,7 @@ import java.nio.IntBuffer; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiFunction; import java.util.logging.Level; import java.util.logging.Logger; import static java.util.stream.Collectors.toSet; @@ -327,7 +328,7 @@ private static long[] getPlatforms() { } @SuppressWarnings("unchecked") - protected void initOpenCL(final long window) { + protected void initOpenCL(final long window, BiFunction sharing) { logger.info("Initialize OpenCL with LWJGL3"); // try { @@ -433,7 +434,7 @@ protected void initOpenCL(final long window) { // create context try { - long context = createContext(platform.getPlatform(), deviceIds, window); + long context = createContext(platform.getPlatform(), deviceIds, window, sharing); clContext = new com.jme3.opencl.lwjgl.LwjglContext(context, (List) chosenDevices); } catch (final Exception ex) { logger.log(Level.SEVERE, "Unable to create OpenCL context", ex); @@ -443,7 +444,7 @@ protected void initOpenCL(final long window) { logger.info("OpenCL context created"); } - private long createContext(final long platform, final long[] devices, long window) { + private long createContext(final long platform, final long[] devices, long window, BiFunction sharing) { try (MemoryStack stack = MemoryStack.stackPush()) { final int propertyCount = 2 + 4 + 1; @@ -453,25 +454,29 @@ private long createContext(final long platform, final long[] devices, long windo // https://github.com/glfw/glfw/issues/104 // https://github.com/LWJGL/lwjgl3/blob/master/modules/core/src/test/java/org/lwjgl/demo/opencl/Mandelbrot.java // TODO: test on Linux and MacOSX - switch (Platform.get()) { - case WINDOWS: - properties.put(KHRGLSharing.CL_GL_CONTEXT_KHR) - .put(org.lwjgl.glfw.GLFWNativeWGL.glfwGetWGLContext(window)) - .put(KHRGLSharing.CL_WGL_HDC_KHR) - .put(org.lwjgl.opengl.WGL.wglGetCurrentDC()); - break; - case LINUX: - properties.put(KHRGLSharing.CL_GL_CONTEXT_KHR) - .put(org.lwjgl.glfw.GLFWNativeGLX.glfwGetGLXContext(window)) - .put(KHRGLSharing.CL_GLX_DISPLAY_KHR) - .put(org.lwjgl.glfw.GLFWNativeX11.glfwGetX11Display()); - break; - case MACOSX: - properties.put(APPLEGLSharing.CL_CONTEXT_PROPERTY_USE_CGL_SHAREGROUP_APPLE) - .put(org.lwjgl.opengl.CGL.CGLGetShareGroup(org.lwjgl.opengl.CGL.CGLGetCurrentContext())); - break; - default: - break; // Unknown Platform, do nothing. + if (sharing == null) { + switch (Platform.get()) { + case WINDOWS: + properties.put(KHRGLSharing.CL_GL_CONTEXT_KHR) + .put(org.lwjgl.glfw.GLFWNativeWGL.glfwGetWGLContext(window)) + .put(KHRGLSharing.CL_WGL_HDC_KHR) + .put(org.lwjgl.opengl.WGL.wglGetCurrentDC()); + break; + case LINUX: + properties.put(KHRGLSharing.CL_GL_CONTEXT_KHR) + .put(org.lwjgl.glfw.GLFWNativeGLX.glfwGetGLXContext(window)) + .put(KHRGLSharing.CL_GLX_DISPLAY_KHR) + .put(org.lwjgl.glfw.GLFWNativeX11.glfwGetX11Display()); + break; + case MACOSX: + properties.put(APPLEGLSharing.CL_CONTEXT_PROPERTY_USE_CGL_SHAREGROUP_APPLE) + .put(org.lwjgl.opengl.CGL.CGLGetShareGroup(org.lwjgl.opengl.CGL.CGLGetCurrentContext())); + break; + default: + break; // Unknown Platform, do nothing. + } + } else { + sharing.apply(properties, window); } properties.put(CL_CONTEXT_PLATFORM).put(platform); diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java index 668739f6f8..5ba2f867c7 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java @@ -472,7 +472,7 @@ public void invoke(final long window, final int width, final int height) { // Create OpenCL if (settings.isOpenCLSupport()) { - initOpenCL(window); + initOpenCL(window, null); } updateSizes(); diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxDefaultGLPlatform.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxDefaultGLPlatform.java index fc0f5777b5..c7058ffcc2 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxDefaultGLPlatform.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxDefaultGLPlatform.java @@ -32,6 +32,8 @@ package com.jme3.system.lwjglx; import org.lwjgl.system.Platform; + +import static org.lwjgl.system.MemoryUtil.*; import static org.lwjgl.system.Platform.*; /** @@ -39,7 +41,12 @@ * @author wil */ public final class LwjglxDefaultGLPlatform { - + + /** + * Detects if you are in a Wayland session. + * + * @return boolean + */ public static boolean isWayland() { Platform platform = Platform.get(); if (platform == LINUX || platform == FREEBSD) { @@ -51,10 +58,28 @@ public static boolean isWayland() { return false; } + /** + * Returns the pointer to a {@code Display*} that uses X11. + * + * @param platform the AWT/GL platform + * @return Display + */ + public static long getX11Display(LwjglxGLPlatform platform) { + if (platform == null) { + return NULL; + } + if (platform instanceof X11GLPlatform) { + return ((X11GLPlatform) platform).getDisplay(); + } + return NULL; + } + /** * Returns a drawing platform based on the platform it is running on. + * * @return LwjglxGLPlatform - * @throws UnsupportedOperationException throws exception if platform is not supported + * @throws UnsupportedOperationException throws exception if platform is not + * supported */ public static LwjglxGLPlatform createLwjglxGLPlatform() throws UnsupportedOperationException { switch (Platform.get()) { diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java index 1bd0da34c5..9a042e7ed4 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java @@ -45,6 +45,10 @@ * @author wil */ final class X11GLPlatform extends PlatformLinuxGLCanvas implements LwjglxGLPlatform { + + public long getDisplay() { + return display; + } /** * (non-Javadoc) From b821ede30dc246b6c2c6a1082bf8bb0d3b4a4298 Mon Sep 17 00:00:00 2001 From: wil Date: Tue, 31 Mar 2026 12:19:23 -0600 Subject: [PATCH 09/14] fix: logger|message --- .../src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index bcc9270805..fd86f94c2c 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -289,7 +289,7 @@ public void lock() { public void unlock() { synchronized (lock) { try { - platformCanvas.unlock();// <- MUST unlock on Linux + platformCanvas.unlock();// <- MUST unlock on Linux } catch (AWTException e) { listener.handleError("Failed to unlock Canvas", e); } @@ -535,7 +535,7 @@ public void run() { } } else { if (!canvas.hasContext()) { - LOGGER.log(Level.FINE, "OGL: Creating display .."); + LOGGER.log(Level.FINE, "AWT: Creating display .."); createContext(settings); reinitContext(); From f20bd7646839f2f9f0715f7327c3c6751e86d65b Mon Sep 17 00:00:00 2001 From: wil Date: Thu, 2 Apr 2026 13:13:30 -0600 Subject: [PATCH 10/14] fix: final configurations | problem with 'disponse()' --- .../com/jme3/system/lwjgl/LwjglCanvas.java | 149 +++++++++++++----- .../com/jme3/system/lwjglx/X11GLPlatform.java | 35 +++- 2 files changed, 138 insertions(+), 46 deletions(-) diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index fd86f94c2c..c3aa089e76 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -38,6 +38,7 @@ import com.jme3.input.awt.AwtKeyInput; import com.jme3.input.awt.AwtMouseInput; import com.jme3.input.lwjgl.SdlJoystickInput; +import com.jme3.math.Vector2f; import com.jme3.system.AppSettings; import com.jme3.system.Displays; import com.jme3.system.JmeCanvasContext; @@ -46,8 +47,11 @@ import java.awt.AWTException; import java.awt.Canvas; import java.awt.Component; +import java.awt.DisplayMode; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; import java.awt.GraphicsConfiguration; import java.awt.Toolkit; import java.awt.event.ComponentAdapter; @@ -493,7 +497,7 @@ public boolean checkVisibilityState() { public void run() { if (listener == null) { throw new IllegalStateException( - "SystemListener is not set on context!" + "Must set with JmeContext.setSystemListener()." + "SystemListener is not set on context! Must set with JmeContext.setSystemListener()." ); } @@ -506,39 +510,40 @@ public void run() { } synchronized (lock) { - if (reinitcontext.getAndSet(false)) { + if (reinitcontext.getAndSet(false)) { LOGGER.log(Level.FINE, "LWJGX: Destroying display .."); - + listener.loseFocus(); if (renderer != null) { renderer.invalidateState(); renderer.cleanup(); } - + canvas.releaseContext(); canvas.deleteContext(); + canvas.doDisposeCanvas(); canvas.context = NULL; - + renderable.set(false); lock.notifyAll(); } } - + if (checkVisibilityState()) { // HACK: All components of the thread hosted in initInt() must be // called after the context is created, but this is only valid // if the canvas is validated by AWT, so it is created at "runtime". - if (!initialize.getAndSet(true)) { + if (!initialize.getAndSet(true)) { if (!initInThread()) { LOGGER.log(Level.SEVERE, "Display initialization failed. Cannot continue."); break; } - } else { + } else { if (!canvas.hasContext()) { LOGGER.log(Level.FINE, "AWT: Creating display .."); createContext(settings); reinitContext(); - + listener.gainFocus(); } } @@ -562,7 +567,7 @@ public void run() { } finally { canvas.unlock(); } - + // Sync the display on some systems. Toolkit.getDefaultToolkit().sync(); } @@ -570,21 +575,16 @@ public void run() { listener.handleError("Error while swapping buffers", ex); } } - } else { - canvas.doDisposeCanvas(); - try { - Thread.sleep(16); - } catch (InterruptedException ignore) { } } - + if (needClose.get()) { break; } } - + deinitInThread(); } - + /** * execute one iteration of the render loop in the OpenGL thread */ @@ -663,62 +663,62 @@ protected void destroyContext() { * @param settings A {@link com.jme3.system.AppSettings} object */ @Override - protected void createContext(AppSettings settings) { + protected void createContext(AppSettings settings) { if (!settings.isX11PlatformPreferred() && isWayland()) { LOGGER.log(Level.WARNING, "LWJGLX and AWT/Swing only work with X11, so XWayland will be used for GLX."); - } - + } + // HACK: For LWJGLX to work in Wyland, it is necessary to use GLX via // XWayland, so LWJGL must be forced to load GLX as a native API. // This is because LWJGLX does not provide an EGL context. if (isWayland()) { Configuration.OPENGL_CONTEXT_API.set("native"); } - + RENDER_CONFIGS.computeIfAbsent(settings.getRenderer(), (t) -> { return (data) -> { data.majorVersion = 2; data.minorVersion = 0; }; }).accept(glData); - + if (settings.getBitsPerPixel() == 24) { glData.redSize = 8; glData.greenSize = 8; - glData.blueSize = 8; + glData.blueSize = 8; } else if (settings.getBitsPerPixel() == 16) { glData.redSize = 5; glData.greenSize = 6; - glData.blueSize = 5; + glData.blueSize = 5; } - + // Enable vsync for LWJGL3-AWT if (settings.isVSync()) { glData.swapInterval = 1; } else { glData.swapInterval = 0; } - + glData.alphaSize = settings.getAlphaBits(); glData.sRGB = settings.isGammaCorrection(); // Not compatible with very old devices - + glData.depthSize = settings.getDepthBits(); glData.stencilSize = settings.getStencilBits(); glData.samples = settings.getSamples(); - glData.stereo = settings.useStereo3D(); - + glData.stereo = settings.useStereo3D(); + glData.debug = settings.isGraphicsDebug(); glData.api = GLData.API.GL; - + allowSwapBuffers = settings.isSwapBuffers(); - + canvas.createContext(); canvas.makeCurrent(); - + // This will activate the "effective data" scrubber. if (settings.getBoolean("GLDataEffectiveDebug")) { LOGGER.log(Level.INFO, "[ DEBUGGER ] :Effective data to initialize the LWJGL3-AWT context\n{0}", - getPrintContextInitInfo(canvas.getGLDataEffective())); + getPrintContextInitInfo(canvas.getGLDataEffective())); } // Create OpenCL if (settings.isOpenCLSupport()) { @@ -799,13 +799,17 @@ public JoyInput getJoyInput() { /** (non-Javadoc) */ @Override public TouchInput getTouchInput() { return null; } /** (non-Javadoc) */ + @Override public void setTitle(String title) { } + /** (non-Javadoc) */ @Override protected void updateSizes() { } /** (non-Javadoc) */ @Override protected void showWindow() { } /** (non-Javadoc) */ @Override protected void setWindowIcon(final AppSettings settings) { } /** (non-Javadoc) */ - @Override public void setTitle(String title) { } + @Override public Vector2f getWindowContentScale(Vector2f store) { + return store == null ? new Vector2f() : store; + } /** * (non-Javadoc) @@ -908,6 +912,14 @@ public int getFramebufferWidth() { return this.framebufferWidth; } + /** + * {@inheritDoc } + */ + @Override + public long getWindowHandle() { + return canvas.context; + } + /** (non-Javadoc) */ @Override public int getWindowXPosition() { @@ -928,10 +940,69 @@ public int getWindowYPosition() { return component.getY(); } - /** (non-Javadoc) */ - @Override public Displays getDisplays() { return null; } - /** (non-Javadoc) */ - @Override public int getPrimaryDisplay() { return 0; } + /** + * {@inheritDoc } + */ + @Override + public Displays getDisplays() { + Displays displays = new Displays(); + GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); + if (environment == null) { + return displays; + } + + GraphicsDevice[] devices = environment.getScreenDevices(); + GraphicsDevice defaultgd = environment.getDefaultScreenDevice(); + if (devices == null || defaultgd == null) { + return displays; + } + + for (int i = 0; i < devices.length; i++) { + GraphicsDevice gd = devices[i]; + DisplayMode mode = gd.getDisplayMode(); + + int width = mode.getWidth(); + int height = mode.getHeight(); + int rate = mode.getRefreshRate(); + + displays.addNewMonitor(i + 1); + displays.setInfo(i, gd.getIDstring(), width, height, rate); + + if (defaultgd.equals(gd)) { + displays.setPrimaryDisplay(i); + } + + LOGGER.log(Level.INFO, "Display id: {0} Resolution: {1} x {2} @ {3}", + new Object[]{gd.getIDstring(), width, height, rate}); + } + + return displays; + } + + /** + * {@inheritDoc } + */ + @Override + public int getPrimaryDisplay() { + GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); + if (environment == null) { + return 0; + } + + GraphicsDevice[] devices = environment.getScreenDevices(); + GraphicsDevice defaultgd = environment.getDefaultScreenDevice(); + if (devices == null || defaultgd == null) { + return 0; + } + + for (int i = 0; i < devices.length; i++) { + GraphicsDevice gd = devices[i]; + if (defaultgd.equals(gd)) { + return i; + } + } + return 0; + } /** * Returns the AWT component where it is drawn (canvas). diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java index 9a042e7ed4..e5930eb7e3 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java @@ -31,24 +31,45 @@ */ package com.jme3.system.lwjglx; -import org.lwjgl.opengl.awt.PlatformLinuxGLCanvas; +import org.lwjgl.opengl.awt.*; + +import static org.lwjgl.opengl.GLX.*; +import static org.lwjgl.system.MemoryUtil.*; import static org.lwjgl.system.jawt.JAWTFunctions.*; /** - * Class X11GLPlatform; overrides the following methods: swapBuffers() - * and makeCurrent(long context). So that the canvas can be removed and - * added back from its parent component. - * - *

- * Works only for Linux based platforms + * X11GLPlatform class that implements the {@link com.jme3.system.lwjglx.LwjglxGLPlatform} + * interface for the Linux (Based) platform. * * @author wil */ final class X11GLPlatform extends PlatformLinuxGLCanvas implements LwjglxGLPlatform { + /** + * Returns a pointer to the {@code Display*} of the current X11 window using + * AWT. + * + * @return long + */ public long getDisplay() { return display; } + + /** + * Delete the previously created context. + * + * @param context long + * @return boolean + */ + @Override + public boolean deleteContext(long context) { + if (context == NULL || display == NULL) { + return false; + } + + glXDestroyContext(display, context); + return true; + } /** * (non-Javadoc) From de97bb9912b3d02551d2ecba0d1f4db5e85e0754 Mon Sep 17 00:00:00 2001 From: wil Date: Thu, 2 Apr 2026 16:49:27 -0600 Subject: [PATCH 11/14] fix: EDT unlock --- .../com/jme3/system/lwjgl/LwjglCanvas.java | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index c3aa089e76..772a7984cc 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -497,35 +497,36 @@ public boolean checkVisibilityState() { public void run() { if (listener == null) { throw new IllegalStateException( - "SystemListener is not set on context! Must set with JmeContext.setSystemListener()." + "SystemListener is not set on context! Must set with JmeContext.setSystemListener()." ); } - LOGGER.log(Level.FINE, "Using LWJGL {0}", Version.getVersion()); + LOGGER.log(Level.FINE, "Using LWJGL {0}", Version.getVersion()); while (true) { if (needResize.getAndSet(false)) { settings.setResolution(framebufferWidth, framebufferHeight); listener.reshape(framebufferWidth, framebufferHeight); } - + synchronized (lock) { if (reinitcontext.getAndSet(false)) { LOGGER.log(Level.FINE, "LWJGX: Destroying display .."); - listener.loseFocus(); - if (renderer != null) { - renderer.invalidateState(); - renderer.cleanup(); - } - - canvas.releaseContext(); - canvas.deleteContext(); - canvas.doDisposeCanvas(); - canvas.context = NULL; + try { + if (renderer != null) { + renderer.invalidateState(); + renderer.cleanup(); + } - renderable.set(false); - lock.notifyAll(); + canvas.releaseContext(); + canvas.deleteContext(); + canvas.doDisposeCanvas(); + canvas.context = NULL; + } finally { + renderable.set(false); + lock.notifyAll(); + } } } From b84dbb37464ec0b8c7bd7178db19c05c9de44108 Mon Sep 17 00:00:00 2001 From: wil Date: Fri, 3 Apr 2026 21:14:37 -0600 Subject: [PATCH 12/14] fix: sleep --- .../src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index 772a7984cc..183ac59bfb 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -576,6 +576,12 @@ public void run() { listener.handleError("Error while swapping buffers", ex); } } + } else { + // HACK: If the GL context is not rendering, the thread will + // enter a waiting state, thus avoiding CPU overload. + try { + Thread.sleep(16); + } catch (InterruptedException ignore) { } } if (needClose.get()) { From d37973af8f5536eb40cfb509747bae4dddbbcebc Mon Sep 17 00:00:00 2001 From: wil Date: Fri, 3 Apr 2026 21:54:10 -0600 Subject: [PATCH 13/14] fix: re-shape|re-scale --- .../com/jme3/system/lwjgl/LwjglCanvas.java | 94 +++++++++++++------ 1 file changed, 67 insertions(+), 27 deletions(-) diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index 183ac59bfb..fa845737e2 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -401,7 +401,7 @@ public Graphics getGraphics() { * Configuration data to start the AWT context, this is used by the * {@code lwjgl-awt} library. */ - private GLData glData; + private final GLData glData; /** Used to notify the canvas status ({@code remove()/add()}). */ private final AtomicBoolean hasNativePeer = new AtomicBoolean(false); @@ -414,20 +414,26 @@ public Graphics getGraphics() { private final AtomicBoolean reinitcontext = new AtomicBoolean(false); /** Notify if there is a change in canvas dimensions. */ - private AtomicBoolean needResize = new AtomicBoolean(false); + private final AtomicBoolean needResize = new AtomicBoolean(false); + /** Notify if there are changes to the canvas scales. */ + private final AtomicBoolean needRescale = new AtomicBoolean(false); /** * Flag that uses the context to check if it is initialized or not, this prevents * it from being initialized multiple times and potentially breaking the JVM. */ - private AtomicBoolean contextFlag = new AtomicBoolean(false); + private final AtomicBoolean contextFlag = new AtomicBoolean(false); /** lock-object. */ private final Object lock = new Object(); - /** Framebuffer width. */ - private int framebufferWidth = 1; + /** Scale of the component in {@code x} */ + private float xScale = 1; + /** Scale of the component in {@code y} */ + private float yScale = 1; + /** Framebuffer width. */ + private int framebufferWidth = 1; /** Framebuffer height. */ private int framebufferHeight = 1; @@ -453,23 +459,7 @@ public LwjglCanvas() { @Override public void componentResized(ComponentEvent e) { synchronized (lock) { - GraphicsConfiguration gc = canvas.getGraphicsConfiguration(); - if (gc == null) { - return; - } - - AffineTransform at = gc.getDefaultTransform(); - float sx = (float) at.getScaleX(), - sy = (float) at.getScaleY(); - - int fw = (int) (canvas.getWidth() * sx); - int fh = (int) (canvas.getHeight() * sy); - - if (fw != framebufferWidth || fh != framebufferHeight) { - framebufferWidth = Math.max(fw, 1); - framebufferHeight = Math.max(fh, 1); - needResize.set(true); - } + updateSizes(); } } }); @@ -509,6 +499,10 @@ public void run() { listener.reshape(framebufferWidth, framebufferHeight); } + if (needRescale.getAndSet(false)) { + listener.rescale(xScale, yScale); + } + synchronized (lock) { if (reinitcontext.getAndSet(false)) { LOGGER.log(Level.FINE, "LWJGX: Destroying display .."); @@ -807,15 +801,61 @@ public JoyInput getJoyInput() { @Override public TouchInput getTouchInput() { return null; } /** (non-Javadoc) */ @Override public void setTitle(String title) { } - /** (non-Javadoc) */ - @Override protected void updateSizes() { } + /** (non-Javadoc) */ @Override protected void showWindow() { } /** (non-Javadoc) */ @Override protected void setWindowIcon(final AppSettings settings) { } - /** (non-Javadoc) */ - @Override public Vector2f getWindowContentScale(Vector2f store) { - return store == null ? new Vector2f() : store; + + /** + * {@inheritDoc } + */ + @Override + protected void updateSizes() { + synchronized (lock) { + GraphicsConfiguration gc = canvas.getGraphicsConfiguration(); + if (gc == null) { + return; + } + + AffineTransform at = gc.getDefaultTransform(); + float sx = (float) at.getScaleX(), + sy = (float) at.getScaleY(); + + int fw = (int) (canvas.getWidth() * sx); + int fh = (int) (canvas.getHeight() * sy); + + if (fw != framebufferWidth || fh != framebufferHeight) { + framebufferWidth = Math.max(fw, 1); + framebufferHeight = Math.max(fh, 1); + needResize.set(true); + } + + if (xScale != sx || yScale != sy) { + xScale = sx; + yScale = sy; + needRescale.set(true); + } + } + } + + /** + * {@inheritDoc } + */ + @Override + public Vector2f getWindowContentScale(Vector2f store) { + if (store == null) store = new Vector2f(); + + GraphicsConfiguration gc = canvas.getGraphicsConfiguration(); + if (gc == null) { + return store; + } + AffineTransform at = gc.getDefaultTransform(); + float sx = (float) at.getScaleX(), + sy = (float) at.getScaleY(); + + store.set(sx, sy); + return store; } /** From acf13a3b2fa7838fb09453b0b3e8dc367e209b1c Mon Sep 17 00:00:00 2001 From: wil Date: Fri, 3 Apr 2026 22:16:24 -0600 Subject: [PATCH 14/14] fix: remove junk code --- .../com/jme3/system/lwjgl/LwjglCanvas.java | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index fa845737e2..955b82f18c 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -806,6 +806,10 @@ public JoyInput getJoyInput() { @Override protected void showWindow() { } /** (non-Javadoc) */ @Override protected void setWindowIcon(final AppSettings settings) { } + /**(non-Javadoc) */ + @Override public Vector2f getWindowContentScale(Vector2f store) { + return store == null ? new Vector2f() : store; + } /** * {@inheritDoc } @@ -839,25 +843,6 @@ protected void updateSizes() { } } - /** - * {@inheritDoc } - */ - @Override - public Vector2f getWindowContentScale(Vector2f store) { - if (store == null) store = new Vector2f(); - - GraphicsConfiguration gc = canvas.getGraphicsConfiguration(); - if (gc == null) { - return store; - } - AffineTransform at = gc.getDefaultTransform(); - float sx = (float) at.getScaleX(), - sy = (float) at.getScaleY(); - - store.set(sx, sy); - return store; - } - /** * (non-Javadoc) * @see com.jme3.system.lwjgl.LwjglContext#printContextInitInfo()