External Publication
Visit Post

Use ImGui in version 3.10.0

jMonkeyEngine Hub May 31, 2026
Source
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

Loading comments...