package me.hammerle.snuviengine.api; import org.lwjgl.glfw.*; import org.lwjgl.opengl.*; import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.system.MemoryUtil.*; public final class Window { private final double secondsPerTick; private final WindowView windowView; private long window = NULL; private Keys keys = null; private Gamepad gamepad = null; private Renderer renderer; private final Stats stats = new Stats(); private int newWidth = -1; private int newHeight = -1; public Window(long nanosPerTick, WindowView windowView) { secondsPerTick = nanosPerTick / 1_000_000_000.0; this.windowView = windowView; } public boolean initialize(String name, int width, int height) { setErrorCallback(); if(initializeGLFW()) { return false; } setWindowHints(); if(createWindow(name, width, height)) { return false; } setKeyCallback(); setGamepad(); setResizeCallback(); createAndSetContext(); setBufferSwapInterval(); return !initializeRenderer(width, height); } private void setErrorCallback() { GLFWErrorCallback.createPrint(System.out).set(); } private boolean initializeGLFW() { return !glfwInit(); } private void setWindowHints() { glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); } private boolean createWindow(String name, int width, int height) { window = glfwCreateWindow(width, height, name, NULL, NULL); if(window == NULL) { terminateGLFW(); return true; } return false; } private void terminateGLFW() { glfwTerminate(); } private void setKeyCallback() { keys = new Keys(); glfwSetKeyCallback(window, (eventWindow, key, scancode, action, mods) -> { if(action == GLFW_PRESS) { keys.press(key); } else if(action == GLFW_RELEASE) { keys.release(key); } }); } private void setGamepad() { gamepad = new Gamepad(GLFW_JOYSTICK_1); } private void setResizeCallback() { glfwSetFramebufferSizeCallback(window, (eventWindow, width, height) -> { newWidth = width; newHeight = height; }); } private void createAndSetContext() { glfwMakeContextCurrent(window); GL.createCapabilities(); } private void setBufferSwapInterval() { glfwSwapInterval(1); } private boolean initializeRenderer(int width, int height) { int shaderProgram = Shader.createShaderProgram("vertex.vs", "fragment.fs"); if(shaderProgram == -1) { return true; } renderer = new Renderer(shaderProgram, width, height, windowView); return false; } public void open(Game game) { game.onCompleteInitialization(stats, keys, gamepad); runGameLoop(game); game.onStop(); destroyWindow(); terminateGLFW(); freeErrorCallback(); } private void runGameLoop(Game game) { double lastSecondTime = glfwGetTime(); double lag = 0; while(!glfwWindowShouldClose(window) && game.isRunning()) { double newSecondTime = glfwGetTime(); lag += newSecondTime - lastSecondTime; lastSecondTime = newSecondTime; while(lag >= secondsPerTick) { lag -= secondsPerTick; tick(game); } renderTick(game, (float) (lag / secondsPerTick)); } } private void tick(Game game) { stats.updateTicksPerSecond(); gamepad.tick(); keys.tick(); game.tick(); } private void renderTick(Game game, float lag) { updateWindowSize(); stats.updateFramesPerSecond(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); game.renderTick(renderer, lag); glfwSwapBuffers(window); glfwPollEvents(); } private void updateWindowSize() { if(newWidth == -1 || newHeight == -1) { return; } int width = windowView.getWidth(newWidth, newHeight); int height = windowView.getHeight(newWidth, newHeight); if(width != newWidth || height != newHeight) { glfwSetWindowSize(window, width, height); } else { newWidth = -1; newHeight = -1; } glViewport(0, 0, width, height); renderer.setSize(width, height, windowView); } private void destroyWindow() { glfwDestroyWindow(window); } private void freeErrorCallback() { GLFWErrorCallback error = glfwSetErrorCallback(null); if(error != null) { error.free(); } } }