OverrunGL 介绍

Java 22 已经发布,其中加入了 FFM API。 本文介绍使用了 FFM API 的 Java 库——OverrunGL。

介绍

OverrunGL 支持访问 C 库。请参考底部的链接获得详细信息。

下载

OverrunGL 支持以 Maven 方式导入。可用生成器生成依赖。网络不佳的环境可能需要 10 分钟加载。

函数调用

OverrunGL 使用接口声明函数,在运行时生成实例。

对比 OverrunGL 和 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();
}

可见,LWJGL 3 使用静态方法,而 OverrunGL 使用实例方法。

对于部分库,用实例封装可要求方法强制传递该库需要的上下文(Context),在多线程环境中可避开ThreadLocal。例如:

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

void drawSomething(GL gl) {
    // 假设 Mesh 类中有 render 方法
    mesh.render(gl);
}

也可用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() {
    // 无需传递参数
    mesh.render();
}

模块化加载

OpenGL 模块支持模块化加载,即只加载需要的函数。见下列示例:

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

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

上面的代码只加载GL10CGL20C,不加载(GL继承的)其他类,提高初始化性能。

提示

自定义状态机,只在状态改变时调用 OpenGL 函数。

 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;

    // 需要添加Skip注解防止误识别为OpenGL函数
    @overrun.marshal.gen.Skip
    void bindTexture2D(int texture) {
        if (textureBinding2D != texture) {
            textureBinding2D = texture;
            bindTexture(TEXTURE_2D, texture);
        }
    }

    // 可选的getter...
}

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

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

内存管理

传统的 JNI 库用long表示内存地址,并用 NIO Buffer 访问内存。OverrunGL 则使用 FFM API 自带的封装MemorySegment

内存分配

MemorySegmentSegmentAllocator创建。 JDK 自带Arena分配器。Arena只允许分配内存,关闭Arena后自动释放它分配的内存。

1
2
3
4
5
try (var arena = Arena.ofConfined()) {
    var seg = arena.allocate(ValueLayout.JAVA_INT);
    //...
}
// 自动释放内存

内存堆栈

内存堆栈(从 LWJGL 3 移植)属于Arena。内存堆栈重用分配的内存。

使用MemoryStack::stackPush

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

零长内存段

本机函数可能返回指针。如果没有指定返回指针的类型,FFM API 将使用零长内存段(zero-length segment)。

开放调用者的权限时,可使用MemorySegment::reinterpret设置大小。

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

空指针

C 中的NULL可用MemorySegment.NULL表示,且等效于MemorySegment.ofAddress(0L)

未来更新

OverrunGL 已支持 GLFW、OpenGL、stb 以及 Native File Dialog。 我们计划在未来添加 Vulkan、OpenAL、FreeType、Zstandard 和 Assimp,并使用 Java 25。

使用 Hugo 构建
主题 StackJimmy 设计