OverrunGL Introduction

Java 22 has been released, and the FFM API is finalized. In this article, I will introduce a library that uses this API called OverrunGL.

Introduction

OverrunGL is a Java library that allows accessing C libraries.

See the links below for more information.

Download

You can import OverrunGL with Maven coordinates. There is a generator for it.

Function Invocation

OverrunGL defines functions with interfaces and generates implementation at runtime.

Compare with LWJGL 3:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// OverrunGL
import overrungl.glfw.GLFW;
GLFW glfw = GLFW.INSTANCE;
void dispose() {
    glfw.terminate();
}

// LWJGL 3
import static org.lwjgl.glfw.GLFW.*;
void dispose() {
    glfwTerminate();
}

You can see that LWJGL 3 uses static methods, but OverrunGL uses instance methods.

For several libraries, instances might be helpful in multithreading context by avoiding ThreadLocal. For example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Imports are omitted
// OpenGL
void main() {
    GLFW glfw = ...;
    GL gl = load(glfw::getProcAddress);
    drawSomething(gl);
}

void drawSomething(GL gl) {
    // Assumes there is a Mesh class with render method
    mesh.render(gl);
}

or ScopedValue:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
static final ScopedValue<GL> OpenGL = ScopedValue.newInstance();
void main() {
    GL gl = ...;
    ScopedValue.runWhere(OpenGL, gl, this::drawSomething);
}

void drawSomething() {
    // No need to pass an argument
    mesh.render();
}

Modular Loading

The OpenGL module has supported modular loading. You can load only the functions you need. See this example:

1
2
3
4
5
6
interface MyFunctions
extends GL10C, GL20C {}

void main() {
    var gl = GLLoader.loadContext(MethodHandles.lookup(), flags, MyFunctions.class);
}

This code only loads GL10C and GL20C. It will not load other classes that GL extends, so it will improve the bootstrap performance.

Tip

An OpenGL state manager which does only invoke GL function when the state is actually changed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
abstract class StateManager implements GL10C, GL11C {
    private int textureBinding2D;

    // Add annotation Skip to avoid identifying as an OpenGL function
    @overrun.marshal.gen.Skip
    void bindTexture2D(int texture) {
        if (textureBinding2D != texture) {
            textureBinding2D = texture;
            bindTexture(TEXTURE_2D, texture);
        }
    }

    // an optional getter here...
}

void main() {
    var gl = GLLoader.loadContext(MethodHandles.lookup(), flags, StateManager.class);
    render(gl);
}

void render(StateManager gl) {
    gl.bindTexture2D(0);
}

Memory Management

OverrunGL uses MemorySegment instead of NIO Buffers. It is provided by the FFM API.

Allocation

Use Arena, which does only allow allocating but not destroying memory manually. The memory is only accessible within the scope of the Arena.

1
2
3
4
5
try (var arena = Arena.ofConfined()) {
    var seg = arena.allocate(ValueLayout.JAVA_INT);
    //...
}
// the memory is automatically released

Memory Stack

Memory stack (implementation from LWJGL 3) is a kind of Arena. It allocates a memory segment once then reuses it.

Use MemoryStack::stackPush to push a frame:

1
2
3
try (var stack = MemoryStack.stackPush()) {
    var seg = stack.allocate(ValueLayout.JAVA_INT);
}

The push and pop operations must be symmetric.

Zero-length Segments

Native functions might return a pointer. If the layout of the returned pointer is not specified, the FFM API will convert it to a zero-length memory segment.

You can resize it with MemorySegment::reinterpret. This requires native access to the module of the caller.

1
--enable-native-access=<module-name>

Null Pointer

NULL in C is modeled as MemorySegment.NULL, which is equivalent to MemorySegment.ofAddress(0L).

Future Updates

OverrunGL has added GLFW, OpenGL, stb and Native File Dialog. We plan to add Vulkan, OpenAL, FreeType, Zstandard and Assimp and use Java 25 in the future.

Built with Hugo
Theme Stack designed by Jimmy