LWJGL + JavaFX + MacOS

Started by Michael Tetzlaff, January 11, 2024, 23:31:50

Previous topic - Next topic

Michael Tetzlaff

Hi, I'm trying to get a project running on MacOS that uses LWJGL and JavaFX.  (This project has run successfully on MacOS a number of years ago, but I haven't tested it in several years, and significant development on the project has happened since then.)

I am currently trying this with LWJGL 3.0.0 and JavaFX 17 (version 17.0.2 from org.openjfx in Maven).  I was previously using LWJGL 3.0.0b and JavaFX 11 (11.0.2).

I am using the XstartOnFirstThread VM argument.  I am running LWJGL / GLFW on the main thread, and launching JavaFX in a separate thread.  I only need LWJGL for offscreen rendering; I'm not trying to create a visible window or poll events.

The basic problem is that it seems that between GLFW and JavaFX, whichever one I initialize second hangs forever on initialization. 

For instance, if I try the following, JavaFX launches fine, but glfwInit() hangs and never returns:

        log.info("Starting JavaFX UI");
        new Thread(() -> kintsugi3d.builder.javafx.MainApplication.launchWrapper("")).start();

        try
        {
            Thread.sleep(5000L);
        }
        catch (InterruptedException e)
        {
            throw new RuntimeException(e);
        }

        if ( glfwInit() != true )
        {
            throw new GLFWException("Unable to initialize GLFW.");
        }

        // finish initializing GLFW, never gets here


(The sleep for 5 seconds is excessive, but just wanted a quick hack to be relatively certain it wasn't a race condition.)

On the other hand, if I use the following code, glfwInit() returns successfully, but my JavaFX application is never created (presumably hanging somewhere in internal JavaFX code):

        if ( glfwInit() != true )
        {
            throw new GLFWException("Unable to initialize GLFW.");
        }


        log.info("Starting JavaFX UI");
        new Thread(() -> kintsugi3d.builder.javafx.MainApplication.launchWrapper("")).start();

        try
        {
            Thread.sleep(5000L);
        }
        catch (InterruptedException e)
        {
            throw new RuntimeException(e);
        }

        // finish initializing GLFW


Any help getting this to work, or even which way is most likely expected to work, would be appreciated.  As I said before, I'm pretty sure I got this to work in the past, but that was before a lot of additional development happened, and probably on different versions of Java/JavaFX/LWJGL/MacOS.

Michael Tetzlaff

I've also now tried updating to LWJGL 3.3.3.  With the latest version of LWJGL, it now seems that if -XstartOnFirstThread is used, JavaFX ALWAYS hangs / fails to launch the window, regardless of initialization order or thread (main vs. non-main).  However, if -XstartOnFirstThread is not used, glfwInit() crashes the program (even when called on the main thread).

spasi

Hey Michael,

Without -XstartOnFirstThread, you must use the async GLFW implementation for macOS. You can do that with Configuration.GLFW_LIBRARY_NAME.set("glfw_async"). This will let you use GLFW from a secondary thread.

I'm not sure how it's going to work with JavaFX though. I've given up on trying to support any kind of integration between JavaFX and GLFW and haven't tested such a configuration. The recommended solution, which doesn't require any interaction with GLFW, is DriftFX: https://github.com/eclipse-efx/efxclipse-drift

Michael Tetzlaff

That works, thanks.

Good to know about DriftFX.  Currently I'm just using framebuffer reads to put the graphics output into the JavaFX window and it works fine, but if I have time to rewrite the graphics backend of my project, it would be nice to have something more integrated.

For anyone else finding this, here's a bit more about what discovered with regards to the relationship between LWJGL, JavaFX, and MacOS:


  • * MacOS requires that all windowing operations happen on the first thread created.  This affects both JavaFX and the GLFW library that LWJGL uses to create windows and graphics contexts.  It gets tricky because both libraries need to be able to use that single thread for all of their windowing-related calls.
  • * JavaFX seems to manage this using some native code that ensures that the JavaFX application thread IS the first thread (so when that Java thread is "started" it's really just running within that first thread).  I'm not 100% sure this is correct, but it's the explanation most consistent with my testing and debugging efforts.
  • * Using -xStartOnFirstThread solves the problem for GLFW by making the Java main() method run on the first thread, but messes up JavaFX as it's no longer able to put the JavaFX application thread on the main thread.
  • * The solution is to NOT use xStartOnFirstThread and use glfw_async, as spasi suggested. I'm not sure entirely how this works, but I'm guessing that, like JavaFX, it has a way of injecting GLFW calls into the first thread.  In addition, my program waits until JavaFX is initialized to start the GLFW thread (which may or may not matter).
  • * An alternative that I had just got working before I saw spasi's response is to put all GLFW calls on the JavaFX application thread, i.e. by putting them in Platform.runLater.  If you do this, then you don't nee to use glfw_async.  Once you have a handle for the GLFW context, that handle can be safely used for most graphics commands on any thread (so the JavaFX thread won't be blocked by graphics operations).  I'm going with spasi's solution though, rather than this, as it also solves other threading issues I encountered.