Java – How to better isolate JOGL or LWJGL3 dependency from game clients

design-patternsgame developmentjavaobject-oriented-designopengl

I'm currently working on a project to develop a relatively small framework with the goal of supporting game development efforts for students in a course. The scope of this project is an OpenGL-based renderer and a 3D scene graph, and must be written in Java. (That's the course pre-req. for students.)

I'm familiar with OpenGL and shader-based programming. I've used JOGL and more recently LWJGL3. One important goal for me is to keep this library dependency hidden and student code ignorant of whatever decision I make here. However, I'm having difficulty in this effort.

While using JOGL and LWJGL3, I've noticed some key design decisions they've made that, in turn, seem to make it more difficult to keep the dependency from being 'leaked' to the client application.

The following is a list of some issues/trade-offs I've identified for both of them:

  • JOGL – Pros
    • Integrates with swing/awt/etc. package components (e.g. can use a JFrame for the OpenGL context and GLCanvas)
    • Allows client application to implement, register, and react to KeyListener, MouseListener, and other interfaces (i.e. students/clients can use the same event-driven input handling methods they've always used.)
    • Familiar 'environment' for students (e.g. using a GLCanvas is no different from using a Canvas, etc.)
  • JOGL – Cons

    • Required implementation of GLEventListener limits where I can make OpenGL calls (not difficult to hide this per se, but client must be able to work with shaders, which internally require OpenGL calls, which requires a valid gl instance)
    • Required GLAutoDrawable object for OpenGL calls is another constraint (obj cannot be cached)
    • Seems to force exposure of the GLAutoDrawable and related objects to client when its code needs to do something and we're not currently within the display(GLAutoDrawable) method override (e.g. client may want to do something like shaderProgram.setAttributeValue("attribName", 0.5f);, but instead has to do shaderProgram.setAttributeValue(gl, "attribName", 0.5f);)
  • LWJGL3 – Pros

    • No requirement for special objects (e.g. GLAutoDrawable) or interface restricting where/when OpenGL calls can be made.
    • Allows me flexibility in designing the client-visible APIs.
  • LWJGL3 – Cons
    • Uses GLFW, with its own Window management API & is incompatible with Java AWT components (e.g. cannot register KeyListener, MouseListener, etc.)
    • It's callback-based (i.e. must use glfwPollEvents(), GLFWKeyCallback, etc)
    • Seems to force exposure of the input-handling callbacks and related objects/functions to client (e.g. if client wants to exit, must define/use GLFWKeyCallback, check GLFW_KEY_ESCAPE, glfwSetWindowShouldClose(...)

For example, to expand a bit more on the bold entries. The client may want to do something like shaderProgram.setAttributeValue("attribName", 0.5f);, but instead would be forced to do shaderProgram.setAttributeValue(gl, "attribName", 0.5f); after calling drawable.getGL() when using JOGL.

On the other hand, when using LWJGL3, if the client wants to handle inputs, it would be forced to do something similar to the code snippet below instead of just being able to react to a keyPressed event by implementing a KeyListener.

glfwSetKeyCallback(window, keyCallback = new GLFWKeyCallback() {
    @Override
    public void invoke(long window, int key, int scancode, int action, int mods) {
        if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
            glfwSetWindowShouldClose(window, GL_TRUE);
        }
    }
});

A few other things I've considered include the following:

  • (LWJGL3) Using the adapter design pattern to hide away the input management of LWJGL3, but this seems to be a significant amount of effort & complication (e.g. map each GLFW_KEY_* to some KeyEvent.VK_* constant) just for a start
  • (LWJGL3) The above would have to be provided as classes rather than interfaces b/c the callback objects would need to be contained (e.g. GLFWKeyCallback) to then deal with the behavior
  • (LWJGL3) A lot of complication/overhead just to handle inputs?…
  • (JOGL) Since the use of the GLAutoDrawable instance can/must not be cached (JOGL manages that internally and docs state your cached reference can become invalid, causing hard-to-track client crashes), perhaps in some way 'caching' calls made by client (e.g. shaderProgram.setAttributeValue(...) and then wait until code is running within display(...) override to apply requested changes, which would add complication and room for bugs that I'd rather live without, etc. (wouldn't even know where/how to start here)
  • Thought about libGDX, but that seems a lot more complicated than what I need, and still uses GLFW internally just like LWJGL3.

Things that I'd find helpful from you guys/gals out there:

  • Thoughts on how you'd deal with this situation
  • Concrete examples that clearly illustrates your point (i.e. code/pseudo-code is fine, just looking for more than "just do X"-type of responses)
  • The more detail you're able/willing to provide, the better-er 🙂

I'll update/clarify my original post as necessary if there're questions.

Thanks to everyone in advance.

Best Answer

I am working on my own Java 3D engine with LWJGL which leads me to my answer below. I don't know much about JOGL.

Both LWJGL and JOGL are just Java bindings to accomplish the same thing, which is to call native code that changes the OpenGL state of the current context. For example, in the current thread, a call in LWJGL to glCreateProgram() will call the native version of glCreateProgram(), while a call in the JOGL version GL2ES2.glCreateProgram() (or whatever it may be) does the exact same thing. Knowing this, you may be able to combine the capabilities wanted from both frameworks.

As I know, a single thread in any application has only one OpenGL context associated with it, so ensure that anything that has to do with OpenGL is called within the same thread, and you should be fine to use both.

In your case, you can try using JOGL for the GUI aspect, and LWJGL for the raw OpenGL calls that you require.