/** * Copyright 2012 Jason Sorensen (sorensenj@smert.net) * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package net.smert.frameworkgl; import java.nio.ByteBuffer; import java.nio.IntBuffer; import org.lwjgl.BufferUtils; import org.lwjgl.glfw.Callbacks; import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFWCursorPosCallback; import org.lwjgl.glfw.GLFWErrorCallback; import org.lwjgl.glfw.GLFWKeyCallback; import org.lwjgl.glfw.GLFWMouseButtonCallback; import org.lwjgl.glfw.GLFWScrollCallback; import org.lwjgl.glfw.GLFWWindowSizeCallback; import org.lwjgl.glfw.GLFWvidmode; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GLContext; import org.lwjgl.system.MemoryUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Jason Sorensen <sorensenj@smert.net> */ public class Window { private final static Logger log = LoggerFactory.getLogger(Window.class); private boolean fullscreen; private boolean initialized; private boolean vSync; private boolean wasResized; private long lastSyncTime; private long variableYieldTime; private long window; private GLFWCursorPosCallback cursorPosCallback; private GLFWErrorCallback errorCallback; private GLFWKeyCallback keyCallback; private GLFWMouseButtonCallback mouseButtonCallback; private GLFWScrollCallback scrollCallback; private GLFWWindowSizeCallback windowSizeCallback; private final IntBuffer heightBuffer; private final IntBuffer widthBuffer; public Window() { fullscreen = Fw.config.fullscreenRequested; vSync = Fw.config.vSyncRequested; heightBuffer = BufferUtils.createIntBuffer(1); widthBuffer = BufferUtils.createIntBuffer(1); } private GLFWvidmode findVideoMode(long monitor, int width, int height, int refreshRate, int redBits, int greenBits, int blueBits) { GLFWvidmode glfwVidMode = null; GLFWvidmode[] modes = getVideoModes(monitor); for (GLFWvidmode mode : modes) { // If the display mode matches save it if ((mode.getWidth() == width) && (mode.getHeight() == height) && (mode.getRefreshRate() == refreshRate) && (mode.getRedBits() == redBits) && (mode.getGreenBits() == greenBits) && (mode.getBlueBits() == blueBits)) { glfwVidMode = mode; // Don't break in case of logging } log.debug("Found fullscreen compatible mode: " + "Width: {}px Height: {}px Refresh Rate: {}hz Red Bits: {} Green Bits: {} Blue Bits: {}", mode.getWidth(), mode.getHeight(), mode.getRefreshRate(), mode.getRedBits(), mode.getGreenBits(), mode.getBlueBits()); } return glfwVidMode; } private GLFWvidmode getDesktopVideoMode(long monitor) { ByteBuffer mode = GLFW.glfwGetVideoMode(monitor); GLFWvidmode glfwVidMode = new GLFWvidmode(); glfwVidMode.setBlueBits(GLFWvidmode.blueBits(mode)); glfwVidMode.setGreenBits(GLFWvidmode.greenBits(mode)); glfwVidMode.setHeight(GLFWvidmode.height(mode)); glfwVidMode.setRedBits(GLFWvidmode.redBits(mode)); glfwVidMode.setRefreshRate(GLFWvidmode.refreshRate(mode)); glfwVidMode.setWidth(GLFWvidmode.width(mode)); return glfwVidMode; } private GLFWvidmode[] getVideoModes(long monitor) { IntBuffer count = BufferUtils.createIntBuffer(1); ByteBuffer modes = GLFW.glfwGetVideoModes(monitor, count); GLFWvidmode[] videoModes = new GLFWvidmode[count.get(0)]; for (int i = 0, max = count.get(0); i < max; i++) { // Advance position modes.position(i * GLFWvidmode.SIZEOF); // Create new video mode and extract data GLFWvidmode glfwVidMode = new GLFWvidmode(); glfwVidMode.setBlueBits(GLFWvidmode.blueBits(modes)); glfwVidMode.setGreenBits(GLFWvidmode.greenBits(modes)); glfwVidMode.setHeight(GLFWvidmode.height(modes)); glfwVidMode.setRedBits(GLFWvidmode.redBits(modes)); glfwVidMode.setRefreshRate(GLFWvidmode.refreshRate(modes)); glfwVidMode.setWidth(GLFWvidmode.width(modes)); // Save video mode to array videoModes[i] = glfwVidMode; } return videoModes; } private void resize(int width, int height) { Fw.config.currentHeight = height; Fw.config.currentWidth = width; wasResized = true; } public void create() { // Update the configuration Configuration config = Fw.config; config.fullscreenEnabled = fullscreen; config.vSyncEnabled = vSync; // Setup variables long monitor = GLFW.glfwGetPrimaryMonitor(); GLFWvidmode videoMode = null; if (fullscreen) { // Attempt to find a matching full screen compatible mode videoMode = findVideoMode(monitor, config.fullscreenWidth, config.fullscreenHeight, config.fullscreenRefreshRate, config.framebufferRedBits, config.framebufferGreenBits, config.framebufferBlueBits); if (videoMode == null) { log.warn("Didn't find a fullscreen display mode for the requested resolution: " + "Width: {}px Height: {}px Refresh Rate: {}hz Red Bits: {} Green Bits: {} Blue Bits: {}", config.fullscreenWidth, config.fullscreenHeight, config.fullscreenRefreshRate, config.framebufferRedBits, config.framebufferGreenBits, config.framebufferBlueBits); } } // Either a full screen mode wasn't requested or we couldn't find one that matched our settings if (videoMode == null) { GLFWvidmode desktopVideoMode = getDesktopVideoMode(monitor); videoMode = new GLFWvidmode(); videoMode.setBlueBits(desktopVideoMode.getBlueBits()); videoMode.setGreenBits(desktopVideoMode.getGreenBits()); videoMode.setHeight(config.desktopHeight); videoMode.setRedBits(desktopVideoMode.getRedBits()); videoMode.setRefreshRate(desktopVideoMode.getRefreshRate()); videoMode.setWidth(config.desktopWidth); } // Reset window hints to defaults before setting them GLFW.glfwDefaultWindowHints(); GLFW.glfwWindowHint(GLFW.GLFW_ALPHA_BITS, videoMode.getBlueBits()); GLFW.glfwWindowHint(GLFW.GLFW_BLUE_BITS, videoMode.getBlueBits()); GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, config.requestedOpenglMajorVersion); GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, config.requestedOpenglMinorVersion); GLFW.glfwWindowHint(GLFW.GLFW_DEPTH_BITS, config.framebufferDepthBits); GLFW.glfwWindowHint(GLFW.GLFW_GREEN_BITS, videoMode.getGreenBits()); GLFW.glfwWindowHint(GLFW.GLFW_RED_BITS, videoMode.getRedBits()); GLFW.glfwWindowHint(GLFW.GLFW_REFRESH_RATE, videoMode.getRefreshRate()); GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, (fullscreen) ? GL11.GL_FALSE : ((config.desktopResizable) ? GL11.GL_TRUE : GL11.GL_FALSE)); if ((config.requestedOpenglMajorVersion >= 3) && (config.requestedOpenglMinorVersion >= 0)) { GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, (config.forwardCompatible) ? GL11.GL_TRUE : GL11.GL_FALSE); } if (((config.requestedOpenglMajorVersion == 3) && (config.requestedOpenglMinorVersion >= 2)) || (config.requestedOpenglMajorVersion >= 4)) { GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, (config.coreProfile) ? GLFW.GLFW_OPENGL_CORE_PROFILE : GLFW.GLFW_OPENGL_COMPAT_PROFILE); } else { GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_ANY_PROFILE); } GLFW.glfwWindowHint(GLFW.GLFW_SAMPLES, config.framebufferSamples); GLFW.glfwWindowHint(GLFW.GLFW_SRGB_CAPABLE, (config.framebufferSrgb) ? GL11.GL_TRUE : GL11.GL_FALSE); GLFW.glfwWindowHint(GLFW.GLFW_STENCIL_BITS, config.framebufferStencilBits); GLFW.glfwWindowHint(GLFW.GLFW_STEREO, (config.framebufferStereo) ? GL11.GL_TRUE : GL11.GL_FALSE); GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GL11.GL_FALSE); // Create new window long fullscreenMonitor = (fullscreen) ? monitor : MemoryUtil.NULL; long newWindow = GLFW.glfwCreateWindow(videoMode.getWidth(), videoMode.getHeight(), config.windowTitle, fullscreenMonitor, window); if (newWindow == MemoryUtil.NULL) { throw new WindowException("Failed to create the GLFW window"); } log.info("Created window with display mode: " + "Width: {}px Height: {}px Refresh Rate: {}hz Red Bits: {} Green Bits: {} Blue Bits: {}", videoMode.getWidth(), videoMode.getHeight(), videoMode.getRefreshRate(), videoMode.getRedBits(), videoMode.getGreenBits(), videoMode.getBlueBits()); // Destroy old window and callback if (window != MemoryUtil.NULL) { GLFW.glfwDestroyWindow(window); cursorPosCallback.release(); keyCallback.release(); mouseButtonCallback.release(); scrollCallback.release(); windowSizeCallback.release(); Fw.input.clearNextState(); } // Resize resize(videoMode.getWidth(), videoMode.getHeight()); // Create GL context GLFW.glfwMakeContextCurrent(newWindow); GLContext.createFromCurrent(); // Set the window location if (!fullscreen) { if ((config.desktopLocationX != -1) && (config.desktopLocationY != -1)) { GLFW.glfwSetWindowPos(newWindow, config.desktopLocationX, config.desktopLocationY); } else { // We can't reuse desktopVideoMode from above since after // we switch out of full screen it would be the full screen // video mode and not the current desktop. GLFWvidmode desktopVideoMode = getDesktopVideoMode(monitor); int x = (desktopVideoMode.getWidth() - config.desktopWidth) / 2; int y = (desktopVideoMode.getHeight() - config.desktopHeight) / 2; GLFW.glfwSetWindowPos(newWindow, x, y); } } // Turn on/off vsync if (vSync) { GLFW.glfwSwapInterval(1); } else { GLFW.glfwSwapInterval(0); } // Events GLFW.glfwSetCursorPosCallback(newWindow, cursorPosCallback = new GLFWCursorPosCallback() { @Override public void invoke(long window, double x, double y) { Fw.input.handleMouseMoveEvent(x, y); } }); GLFW.glfwSetKeyCallback(newWindow, keyCallback = new GLFWKeyCallback() { @Override public void invoke(long window, int key, int scancode, int action, int mods) { Fw.input.addKeyboardEvent(key, mods, scancode, action != GLFW.GLFW_RELEASE); } }); GLFW.glfwSetMouseButtonCallback(newWindow, mouseButtonCallback = new GLFWMouseButtonCallback() { @Override public void invoke(long window, int button, int action, int mods) { Fw.input.addMouseEvent(button, mods, action != GLFW.GLFW_RELEASE); } }); GLFW.glfwSetScrollCallback(newWindow, scrollCallback = new GLFWScrollCallback() { @Override public void invoke(long window, double xoffset, double yoffset) { Fw.input.handleMouseScrollEvent(xoffset, yoffset); } }); GLFW.glfwSetWindowSizeCallback(newWindow, windowSizeCallback = new GLFWWindowSizeCallback() { @Override public void invoke(long window, int width, int height) { resize(width, height); } }); // Show the window GLFW.glfwShowWindow(newWindow); // Save window window = newWindow; // Hide/unhide the cursor Fw.input.updateInputMode(); } public void destroy() { if (!initialized) { return; } GLFW.glfwDestroyWindow(window); GLFW.glfwTerminate(); cursorPosCallback.release(); errorCallback.release(); keyCallback.release(); mouseButtonCallback.release(); scrollCallback.release(); windowSizeCallback.release(); window = MemoryUtil.NULL; initialized = false; } public void destroyWindow() { if (window == MemoryUtil.NULL) { return; } GLFW.glfwDestroyWindow(window); window = MemoryUtil.NULL; } public int getHeight() { GLFW.glfwGetWindowSize(window, widthBuffer, heightBuffer); return heightBuffer.get(0); } public int getWidth() { GLFW.glfwGetWindowSize(window, widthBuffer, heightBuffer); return widthBuffer.get(0); } public long getWindow() { return window; } public boolean isCloseRequested() { return (GLFW.glfwWindowShouldClose(window) == GL11.GL_TRUE); } public boolean isFocused() { return (GLFW.glfwGetWindowAttrib(window, GLFW.GLFW_FOCUSED) == GL11.GL_TRUE); } public boolean wasResized() { return wasResized; } public void setWasResized(boolean wasResized) { this.wasResized = wasResized; } public void init() { if (initialized) { return; } GLFW.glfwSetErrorCallback(errorCallback = Callbacks.errorCallbackThrow()); if (GLFW.glfwInit() != GL11.GL_TRUE) { GLFW.glfwTerminate(); throw new WindowException("Unable to initialize GLFW"); } initialized = true; } public void printDisplayModes() { // These modes should always be fullscreen compatible long monitor = GLFW.glfwGetPrimaryMonitor(); GLFWvidmode[] videoModes = getVideoModes(monitor); for (GLFWvidmode videoMode : videoModes) { System.out.println("Found fullscreen compatible mode: " + "Width: " + videoMode.getWidth() + "px Height: " + videoMode.getHeight() + "px Refresh Rate: " + videoMode.getRefreshRate() + "hz Red Bits: " + videoMode.getRedBits() + " Green Bits: " + videoMode.getGreenBits() + " Blue Bits: " + videoMode.getBlueBits()); } } public void processMessages() { GLFW.glfwPollEvents(); } public void setTitle(String title) { GLFW.glfwSetWindowTitle(window, title); } public void sync(int fps) { if (fps <= 0) { return; } long overSleep = 0; long sleepTime = Timer.ONE_NANO_SECOND / fps; long yieldTime = Math.min(sleepTime, variableYieldTime + sleepTime % (1000000L)); try { while (true) { long t = System.nanoTime() - lastSyncTime; if (t < sleepTime - yieldTime) { Thread.sleep(1); } else if (t < sleepTime) { Thread.yield(); } else { overSleep = t - sleepTime; break; } } } catch (InterruptedException e) { // Do nothing } finally { lastSyncTime = System.nanoTime() - Math.min(overSleep, sleepTime); if (overSleep > variableYieldTime) { variableYieldTime = Math.min(variableYieldTime + 200 * 1000, sleepTime); // Increse 200 microseconds } else if (overSleep < variableYieldTime - 200 * 1000) { variableYieldTime = Math.max(variableYieldTime - 2 * 1000, 0); // Decrease 2 microseconds } } } public void toggleFullscreen() { fullscreen = !fullscreen; create(); } public void toggleVSync() { vSync = !vSync; create(); } public void update() { GLFW.glfwSwapBuffers(window); } }