Use ImGui in version 3.10.0
jMonkeyEngine Hub
May 31, 2026
package jmeimgui;
import com.jme3.system.JmeContext;
import com.jme3.system.JmeSystem;
import com.jme3.system.Platform;
import com.jme3.system.lwjgl.LwjglWindow;
import imgui.ImDrawData;
import imgui.ImGui;
import imgui.ImGuiIO;
import imgui.flag.ImGuiConfigFlags;
import imgui.gl3.ImGuiImplGl3;
import imgui.type.ImInt;
import java.nio.ByteBuffer;
import static org.lwjgl.opengl.GL11.GL_LINEAR;
import static org.lwjgl.opengl.GL11.GL_RENDERER;
import static org.lwjgl.opengl.GL11.GL_RGBA;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_BINDING_2D;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL11.GL_VENDOR;
import static org.lwjgl.opengl.GL11.GL_VERSION;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL11.glGenTextures;
import static org.lwjgl.opengl.GL11.glGetInteger;
import static org.lwjgl.opengl.GL11.glGetString;
import static org.lwjgl.opengl.GL11.glTexImage2D;
import static org.lwjgl.opengl.GL11.glTexParameteri;
import static org.lwjgl.opengl.GL11C.GL_TEXTURE_MIN_FILTER;
import static org.lwjgl.opengl.GL20.GL_SHADING_LANGUAGE_VERSION;
/**
* JME3与ImGui集成类 - 已适配 JME 3.10.0-alpha5 及 LWJGL 3.4.1 SDL3 后端
*
* @author Icyboxs
*/
public class JmeImGui {
private final ImGuiImplGl3 imGuiGl3 = new ImGuiImplGl3();
private long windowHandle;
private boolean initialized = false;
// 【关键修改点 1】:新增成员变量,用来就地缓存初始化时喂进来的上下文句柄
private JmeContext cachedContext;
/**
* 初始化ImGui - 适配1.92.0+ 与 SDL 窗口上下文
*/
/**
* 初始化ImGui - 适配 1.92.0+ 与新版 JME3.10 运行期上下文抢道冲突
*/
public void init(JmeContext context, Runnable beforeInit) {
this.cachedContext = context;
this.windowHandle = ((LwjglWindow) context).getWindowHandle(); //
// 创建ImGui上下文
ImGui.createContext(); //
// 获取IO配置并启用新特性
ImGuiIO io = ImGui.getIO(); //
io.setIniFilename(null); // 禁用ini文件保存
// 启用常规特性
io.addConfigFlags(ImGuiConfigFlags.DockingEnable); //
// 执行自定义初始化回调
if (beforeInit != null) {
beforeInit.run(); //
}
// 告诉 ImGui 现在的底层平台环境,阻止其走入 GLFW 的事件劫持逻辑
try {
io.setBackendPlatformName("imgui_impl_sdl3");
io.setBackendRendererName("imgui_impl_opengl3");
} catch (NoSuchMethodError e) {
// 兼容防抱死
}
try {
if (org.lwjgl.opengl.GL.getCapabilities() == null) {
// 如果发现真的没初始化(虽然基本不可能),再让它安全创建
org.lwjgl.opengl.GL.createCapabilities();
}
} catch (IllegalStateException e) {
// 万一底层还在报错,直接强行注入 JME 当前已经绑定好的原生指针环境
// 彻底杜绝 LWJGL 抛出 "setFunctionMissingAddresses has been called already"
try {
java.lang.reflect.Method makeCurrent = org.lwjgl.opengl.GL.class.getDeclaredMethod("makeCurrent", org.lwjgl.opengl.GLCapabilities.class);
makeCurrent.setAccessible(true);
// 动态获取当前活动的句柄实例,让其复用
org.lwjgl.opengl.GLCapabilities caps = org.lwjgl.opengl.GL.createCapabilities(false);
makeCurrent.invoke(null, caps);
} catch (Exception ex) {
// 忽略反射异常,确保静默向下通行
}
}
// ============================================================
// 此时初始化已经绝对安全,直接通行!
imGuiGl3.init(decideGlslVersion()); //
imGuiGl3.newFrame(); //
initialized = true;
}
public void init(JmeContext context) {
init(context, null); //
}
/**
* 开始新帧 - 彻底修复 JmeSystem 静态调用符号缺失问题
*/
public void startFrame() {
if (!initialized) {
return;
}
imgui.ImGuiIO io = ImGui.getIO();
// 【关键修改点 3】:直接读取 cachedContext 成员,完美榨取宽度、高度和计时器信息
try {
if (cachedContext != null && cachedContext.getSettings() != null) {
com.jme3.system.AppSettings settings = cachedContext.getSettings();
io.setDisplaySize((float) settings.getWidth(), (float) settings.getHeight());
if (cachedContext.getTimer() != null) {
float tpf = cachedContext.getTimer().getTimePerFrame();
io.setDeltaTime(tpf > 0 ? tpf : 1.0f / 60.0f);
} else {
io.setDeltaTime(1.0f / 60.0f);
}
} else {
io.setDisplaySize(1920f, 1080f);
io.setDeltaTime(1.0f / 60.0f);
}
} catch (Exception e) {
io.setDisplaySize(1920f, 1080f);
io.setDeltaTime(1.0f / 60.0f);
}
// 正式步入 Dear ImGui 的核心渲染生命周期
ImGui.newFrame();
}
/**
* 结束帧并渲染
*/
public void endFrame() {
if (!initialized || ImGui.getCurrentContext() == null) {
return;
}
try {
ImGui.render(); //
final ImDrawData drawData = ImGui.getDrawData(); //
if (!isValidDrawData(drawData)) { //
return;
}
// 渲染主视口
imGuiGl3.renderDrawData(drawData); //
} catch (Exception e) {
System.err.println("ImGui渲染错误: " + e.getClass().getSimpleName());
e.printStackTrace();
}
}
/**
* 验证绘制数据有效性
*/
private boolean isValidDrawData(ImDrawData drawData) {
if (drawData == null) { //
return false;
}
if (!drawData.getValid()) { //
return false;
}
try {
float width = drawData.getDisplaySizeX(); //
float height = drawData.getDisplaySizeY(); //
return width > 0 && height > 0; //
} catch (Exception e) {
return false;
}
}
/**
* 刷新字体纹理
*/
public void refreshFontTexture() {
if (!initialized) {
return;
}
ImInt fontW = new ImInt(); //
ImInt fontH = new ImInt(); //
ImGuiIO imGuiIO = ImGui.getIO(); //
ByteBuffer fontData = imGuiIO.getFonts().getTexDataAsRGBA32(fontW, fontH); //
int originalTexture = glGetInteger(GL_TEXTURE_BINDING_2D); //
int fontTexture = glGenTextures(); //
glBindTexture(GL_TEXTURE_2D, fontTexture); //
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); //
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fontW.get(), fontH.get(), 0, GL_RGBA, GL_UNSIGNED_BYTE, fontData); //
imGuiIO.getFonts().setTexID(fontTexture); //
glBindTexture(GL_TEXTURE_2D, originalTexture); //
}
/**
* 释放资源
*/
public void dispose() {
if (!initialized) {
return;
}
try {
ImGui.destroyContext(); //
initialized = false;
System.out.println("ImGui资源已释放");
} catch (Exception e) {
System.err.println("ImGui资源释放错误: " + e.getMessage());
}
}
/**
* 判断GLSL版本
*/
protected String decideGlslVersion() {
Platform.Os os = JmeSystem.getPlatform().getOs(); //
if (os == Platform.Os.Windows) { //
return "#version 150"; //
} else if (isOpenGLVersionSupported(3, 3)) { //
return "#version 330"; //
} else {
return "#version 130"; //
}
}
/**
* 检查是否支持特定的OpenGL版本
*/
public boolean isOpenGLVersionSupported(int major, int minor) {
try {
String version = getOpenGLVersion();
if (version == null || version.equals("Unknown")) {
return false;
}
String[] parts = version.split("\\s+")[0].split("\\.");
if (parts.length >= 2) {
int actualMajor = Integer.parseInt(parts[0]);
int actualMinor = Integer.parseInt(parts[1]);
if (actualMajor > major) {
return true;
}
if (actualMajor == major && actualMinor >= minor) {
return true;
}
}
return false;
} catch (Exception e) {
return false;
}
}
public String getOpenGLVersion() {
try {
String version = glGetString(GL_VERSION);
return version != null ? version : "Unknown";
} catch (Exception e) {
return "Error: " + e.getMessage();
}
}
public String getOpenGLVendor() {
try {
String vendor = glGetString(GL_VENDOR);
return vendor != null ? vendor : "Unknown";
} catch (Exception e) {
return "Error: " + e.getMessage();
}
}
public String getOpenGLRenderer() {
try {
String renderer = glGetString(GL_RENDERER);
return renderer != null ? renderer : "Unknown";
} catch (Exception e) {
return "Error: " + e.getMessage();
}
}
public String getGLSLVersion() {
try {
String glslVersion = glGetString(GL_SHADING_LANGUAGE_VERSION);
return glslVersion != null ? glslVersion : "Unknown";
} catch (Exception e) {
return "Error: " + e.getMessage();
}
}
public void printOpenGLInfo() {
System.out.println("=== OpenGL 系统信息 ===");
System.out.println("版本: " + getOpenGLVersion());
System.out.println("供应商: " + getOpenGLVendor());
System.out.println("渲染器: " + getOpenGLRenderer());
System.out.println("GLSL版本: " + getGLSLVersion());
System.out.println("ImGui版本: " + ImGui.getVersion());
System.out.println("=====================");
}
public void showOpenGLInfoWindow() {
if (!initialized) {
return;
}
ImGui.begin("OpenGL 信息");
ImGui.text("OpenGL 版本: " + getOpenGLVersion());
ImGui.text("供应商: " + getOpenGLVendor());
ImGui.text("渲染器: " + getOpenGLRenderer());
ImGui.text("GLSL 版本: " + getGLSLVersion());
ImGui.text("ImGui 版本: " + ImGui.getVersion());
ImGui.text("多视口支持: " + (ImGui.getIO().hasConfigFlags(ImGuiConfigFlags.ViewportsEnable) ? "是" : "否"));
ImGui.text("停靠支持: " + (ImGui.getIO().hasConfigFlags(ImGuiConfigFlags.DockingEnable) ? "是" : "否"));
if (ImGui.button("复制到剪贴板")) {
String info = String.format(
"OpenGL版本: %s\n供应商: %s\n渲染器: %s\nGLSL版本: %s\nImGui版本: %s",
getOpenGLVersion(), getOpenGLVendor(), getOpenGLRenderer(), getGLSLVersion(), ImGui.getVersion()
);
ImGui.setClipboardText(info);
}
ImGui.end();
}
public boolean isNvidiaGPU() {
String vendor = getOpenGLVendor().toLowerCase();
return vendor.contains("nvidia");
}
public boolean isAMDGPU() {
String vendor = getOpenGLVendor().toLowerCase();
return vendor.contains("amd") || vendor.contains("ati");
}
public boolean isIntelGPU() {
String vendor = getOpenGLVendor().toLowerCase();
return vendor.contains("intel");
}
public String getOpenGLInfoReport() {
return String.format(
"OpenGL 系统信息报告:\n"
+ "=====================\n"
+ "版本: %s\n供应商: %s\n渲染器: %s\n"
+ "GLSL版本: %s\n显卡类型: %s\nImGui版本: %s\n"
+ "支持OpenGL 3.3+: %s\n支持OpenGL 4.0+: %s\n"
+ "多视口: %s\n停靠: %s\n"
+ "=====================",
getOpenGLVersion(), getOpenGLVendor(), getOpenGLRenderer(),
getGLSLVersion(), getGPUType(), ImGui.getVersion(),
isOpenGLVersionSupported(3, 3), isOpenGLVersionSupported(4, 0),
ImGui.getIO().hasConfigFlags(ImGuiConfigFlags.ViewportsEnable),
ImGui.getIO().hasConfigFlags(ImGuiConfigFlags.DockingEnable)
);
}
public String getGPUType() {
if (isNvidiaGPU()) {
return "NVIDIA";
}
if (isAMDGPU()) {
return "AMD";
}
if (isIntelGPU()) {
return "Intel";
}
return "其他/未知";
}
public boolean isInitialized() {
return initialized;
}
public String getImGuiVersion() {
return ImGui.getVersion();
}
}
JME3 and ImGui Integration Class - Compatible with JME 3.10.0-alpha5 and LWJGL 3.4.1 SDL3 backend
Unfortunately, the Angle renderer cannot use ImGui. Some small toys I made using the jme3.6.0 version run well in the 3.10.0-alpha5 version of the OpenGL renderer. However, in the Angle renderer, there seem to be many problems.
I haven’t yet understood how the Angle renderer works. The issues encountered with the Angle renderer cannot be fixed at present.
Discussion in the ATmosphere