JVM crashing after switching to fullscreen

Started by Andrew_3ds, December 11, 2015, 01:02:05

Previous topic - Next topic

Andrew_3ds

So I've spent a considerable amount of time trying to figure this out. I'm trying to implement a way to switch to windowed/fullscreen at runtime instead of having to restart the game to make the effects work. For some reason, it crashes on the glDrawElements() call in OpenGL 3.3 core. It works when I test with a simple glBegin() draw, but crashes when I use my engine. The JNI crash log doesn't give me much information on why it's happening and I'm really confused. I created a wrapper library for GLFW to make windowing simpler. Here is the code from the engine in which I switch the window to fullscreen:
if(pressedKeys.contains(Keys.lAlt.intValue) && pressedKeys.contains(keyCode('\n'))) {
    key = 0;
    boolean fullscreen = !GameConfig.getSettings().isFullscreen();
    CoreStructure.Window.turnIntoFullscreen(fullscreen);
    initCallbacks();

    GameConfig.getSettings().setFullscreen(fullscreen);
}


Which produces this JNI crash log at glDrawElements():
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000000006ad64064, pid=1100, tid=6468
#
# JRE version: Java(TM) SE Runtime Environment (8.0_65-b17) (build 1.8.0_65-b17)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.65-b01 mixed mode windows-amd64 compressed oops)
# Problematic frame:
# C  [nvoglv64.DLL+0x894064]
#
# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# C:\Users\Andrew\Documents\IdeaProjects\Game Engine\hs_err_pid1100.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#


And here is the source for my wrapper library:
package com.productions.windowing;

import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GLCapabilities;

import java.nio.DoubleBuffer;
import java.nio.IntBuffer;

import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.system.MemoryUtil.NULL;

/**
 *
 * @author Andrew
 */
public class GLWindow {
    Long WindowID;
    
    public static GLFWVidMode Screen;
    private static boolean fullscreen;
    private static long MODE;
    private static GLCapabilities caps;
    
    IntBuffer W;
    IntBuffer H;
    DoubleBuffer mX;
    DoubleBuffer mY;

    boolean focused;

    Callbacks callbacks;

    public static boolean Init() {
        boolean init = (glfwInit() == GL_TRUE);
            if(!init) return false;

        Screen = glfwGetVideoMode(glfwGetPrimaryMonitor());
        MODE = NULL;

        glfwSetErrorCallback(Callbacks.error = GLFWErrorCallback.createPrint(System.err));

        return true;
    }

    public GLWindow(Integer[] Size, String title) {
        WindowID = glfwCreateWindow(Size[0],Size[1],title,MODE,NULL);
        if(WindowID == NULL) {
            throw new IllegalStateException("Failed to create window");
        }
        glfwSetWindowPos(WindowID,
                (Screen.width() - Size[0]) / 2,
                (Screen.height() - Size[1]) / 2);

        W = BufferUtils.createIntBuffer(1);
        H = BufferUtils.createIntBuffer(1);
        mX = BufferUtils.createDoubleBuffer(1);
        mY = BufferUtils.createDoubleBuffer(1);
        createCallbacks();
    }

    public Long getWindowID() {
        return this.WindowID;
    }

    public void turnIntoFullscreen(boolean fullscreen) {
        setFullscreen(fullscreen);
        long newWindow = glfwCreateWindow(getWidth(),getHeight(),"",MODE,this.getWindowID());
        glfwDestroyWindow(WindowID);
        WindowID = newWindow;
        glfwSetWindowPos(WindowID,
                (Screen.width() - getWidth()) / 2,
                (Screen.height() - getHeight()) / 2);
        transferContextToNewThread();
        show();
    }

    private void createCallbacks() {
        callbacks = new Callbacks();

        glfwSetWindowFocusCallback(WindowID, callbacks.focus = new GLFWWindowFocusCallback() {
            @Override
            public void invoke(long window, int focus) {
                focused = (focus == GL_TRUE);
            }
        });
    }

    public void setTitle(String title) {
        glfwSetWindowTitle(getWindowID(), title);
    }

    public boolean isFocused() {
        return focused;
    }

    public int getWidth() {
        updateSize();
        return W.get(0);
    }

    public int getHeight() {
        updateSize();
        return H.get(0);
    }

    public void setSize(int width, int height) {
        glfwSetWindowSize(this.getWindowID(),width,height);
    }

    public int getMouseX() {
        updateMousePos();
        return (int) mX.get(0);
    }

    public int getMouseY() {
        updateMousePos();
        return (int) mY.get(0);
    }

    public void centerMouse() {
        glfwSetCursorPos(WindowID, getWidth() / 2, getHeight() / 2);
    }

    public void toggleCursor() {
        if(glfwGetInputMode(WindowID, GLFW_CURSOR) == GLFW_CURSOR_HIDDEN) {
            glfwSetInputMode(WindowID, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
        } else glfwSetInputMode(WindowID, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
    }

    public void setIcon() {
        return;
    }
    
    private void updateSize() {
        W.clear();
        H.clear();
        glfwGetWindowSize(WindowID, W, H);
    }

    private void updateMousePos() {
        mX.clear();
        mY.clear();
        glfwGetCursorPos(WindowID, mX, mY);
    }

    public void createContext() {
        glfwMakeContextCurrent(WindowID);
        caps = GL.createCapabilities();
    }

    public void transferContextToNewThread() {
        glfwMakeContextCurrent(WindowID);
        GL.setCapabilities(caps);
    }

    public void destroy() {
        callbacks.cleanUp();
        glfwDestroyWindow(WindowID);
    }

    public void update() {
        glfwSwapBuffers(WindowID);
    }

    public void close() {
        glfwSetWindowShouldClose(WindowID,GL_TRUE);
    }

    public boolean isCloseRequested() {
        return glfwWindowShouldClose(WindowID) != GL_FALSE;
    }

    public static void hints() {
        glfwDefaultWindowHints();
        glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
        glfwWindowHint(GLFW_VISIBLE, GL_FALSE);
        glfwWindowHint(GLFW_REFRESH_RATE, 60);
    }

    public static void setGLVersion(int major, int minor) {
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, major);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, minor);

        if(major >= 3 && minor >= 2) {
            glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
            glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
        }
    }

    public static void setResizable(boolean value) {
        glfwWindowHint(GLFW_RESIZABLE,value?GL_TRUE:GL_FALSE);
    }

    public boolean isKeyDown(int key) {
        return glfwGetKey(WindowID, key) == GLFW_PRESS;
    }

    public boolean isMouseButtonDown(int btn) {
        return glfwGetMouseButton(WindowID, btn) == GLFW_PRESS;
    }

    public static void setFullscreen(boolean value) {
        fullscreen = value;
        if(value)
            MODE = glfwGetPrimaryMonitor();
        else MODE = NULL;
    }

    public boolean isFullscreen() {
        return fullscreen;
    }

    public void show() {
        glfwShowWindow(this.WindowID);
    }

    public void hide() {
        glfwHideWindow(this.WindowID);
    }

    public void setSizeConstraints(int minWidth, int minHeight, int maxWidth, int maxHeight) {
        glfwSetWindowSizeLimits(WindowID, minWidth, minHeight, maxWidth, maxHeight);
    }

    public void setScreenPosition(int x, int y) {
        glfwSetWindowPos(this.WindowID,x,y);
    }

    public void setVSync(boolean enabled) {
        glfwSwapInterval(enabled?1:0);
    }

    public void setKeyCallback(GLFWKeyCallback key) {
        glfwSetKeyCallback(WindowID, callbacks.key = key);
    }

    public void setScrollCallback(GLFWScrollCallback scroll) {
        glfwSetScrollCallback(WindowID, callbacks.scroll = scroll);
    }

    public void setResizeCallback(GLFWWindowSizeCallback resize) {
        glfwSetWindowSizeCallback(WindowID, callbacks.resize = resize);
    }
}


Any insight will be much appreciated.

spasi

One problem I see with the above code is the transferContextToNewThread() method. You should remove it and use createContext() instead, each new context requires a new call to GL.createCapabilities(). You're very likely going to get the exact same data, but technically the driver is free to return different function pointers for different contexts. GL.setCapabilities(caps) is useful when you have already created a context and want to transfer it to another thread.

Other than that, the crash is likely caused by some GL state you haven't transferred correctly to the new context. For example, check your VAO(s), those aren't shared between contexts.

Also, you may want to consider that GLFW 3.2 is going to support window/fullscreen mode switching without destroying the GL context. No idea when it's going to be ready though.

abcdef

When you create the new window you also need to recreate the callbacks, shaders, textures etc.

spasi

Quote from: abcdef on December 11, 2015, 08:36:24When you create the new window you also need to recreate the callbacks, shaders, textures etc.

Notice that he's sharing the context of the previous window when creating the new one. This means that all shaders/textures/VBOs/etc. survive and do not need to be recreated. Only the state of the new context needs to be set correctly, which includes state objects that are not shared between contexts (e.g. VAOs and FBOs).

abcdef

Ah that's good to know, I didn't realise you could do that.