OverrunGL 全称为 Overrun Game Library,顾名思义就是由 Overrun Organization 开发的游戏库(实际上和游戏没什么关系)。本文介绍 OverrunGL 相对于 LWJGL 3 的区别和特点。
介绍
OverrunGL 使用 Java 23 编写,并利用 Java 22 加入的 FFM API 来访问各种 C 库。请参考底部的链接获得详细信息。
下载
OverrunGL 支持以 Maven 方式导入。可用生成器生成依赖。
函数调用
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);
}
|
上面的代码只加载GL10C
和GL20C
,不加载(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
。
内存分配
MemorySegment
由SegmentAllocator
创建。JDK 自带Arena
分配器,其只允许分配内存,关闭后自动释放所分配的内存。
1
2
3
4
5
| try (var arena = Arena.ofConfined()) {
var seg = arena.allocate(ValueLayout.JAVA_INT);
//...
}
// 自动释放内存
|
内存堆栈
JDK 自带的 Arena 不支持重复使用内存,在多次分配少量内存时效率相对低下,故需要使用内存堆栈保存并重用分配的内存。
OverrunGL 使用 memstack 实现方法中少量内存的分配。memstack 的基本用法如下:
1
2
3
| try (var stack = MemoryStack.pushLocal()) {
var seg = stack.allocate(ValueLayout.JAVA_INT);
}
|
零长内存段
C 库中的函数可能返回指针,如果没有指定返回指针的类型,FFM API 将使用零长内存段(zero-length segment),其只表示一个地址。
开放调用者的权限后,可使用MemorySegment::reinterpret
设置内存段的大小。
1
| --enable-native-access=<模块名>
|
空指针
C 中的NULL
可用MemorySegment.NULL
表示,且等效于MemorySegment.ofAddress(0L)
。
未来更新
OverrunGL 已支持 GLFW、OpenGL、stb 以及 (Extended) Native File Dialog。
我们计划在未来添加 Vulkan、OpenAL、FreeType、Zstandard 和 Assimp,并使用 Java 25。