diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java index d5e515410..f4f05b4a8 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java @@ -43,19 +43,69 @@ import com.jme3.system.AppSettings; import com.jme3.system.JmeContext; import com.jme3.system.JmeSystem; import com.jme3.system.NanoTimer; -import org.lwjgl.glfw.*; +import com.jme3.util.BufferUtils; + +import org.lwjgl.Version; +import org.lwjgl.glfw.GLFWErrorCallback; +import org.lwjgl.glfw.GLFWImage; +import org.lwjgl.glfw.GLFWVidMode; +import org.lwjgl.glfw.GLFWWindowFocusCallback; +import org.lwjgl.glfw.GLFWWindowSizeCallback; +import org.lwjgl.stb.STBImage; import java.awt.*; import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.IntBuffer; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; -import org.lwjgl.Version; -import static org.lwjgl.glfw.GLFW.*; +import javax.imageio.ImageIO; + +import static org.lwjgl.glfw.GLFW.GLFW_ALPHA_BITS; +import static org.lwjgl.glfw.GLFW.GLFW_BLUE_BITS; +import static org.lwjgl.glfw.GLFW.GLFW_CONTEXT_VERSION_MAJOR; +import static org.lwjgl.glfw.GLFW.GLFW_CONTEXT_VERSION_MINOR; +import static org.lwjgl.glfw.GLFW.GLFW_DEPTH_BITS; +import static org.lwjgl.glfw.GLFW.GLFW_DOUBLEBUFFER; +import static org.lwjgl.glfw.GLFW.GLFW_FALSE; +import static org.lwjgl.glfw.GLFW.GLFW_GREEN_BITS; +import static org.lwjgl.glfw.GLFW.GLFW_OPENGL_CORE_PROFILE; +import static org.lwjgl.glfw.GLFW.GLFW_OPENGL_DEBUG_CONTEXT; +import static org.lwjgl.glfw.GLFW.GLFW_OPENGL_FORWARD_COMPAT; +import static org.lwjgl.glfw.GLFW.GLFW_OPENGL_PROFILE; +import static org.lwjgl.glfw.GLFW.GLFW_RED_BITS; +import static org.lwjgl.glfw.GLFW.GLFW_REFRESH_RATE; +import static org.lwjgl.glfw.GLFW.GLFW_RESIZABLE; +import static org.lwjgl.glfw.GLFW.GLFW_SAMPLES; +import static org.lwjgl.glfw.GLFW.GLFW_SRGB_CAPABLE; +import static org.lwjgl.glfw.GLFW.GLFW_STENCIL_BITS; +import static org.lwjgl.glfw.GLFW.GLFW_STEREO; +import static org.lwjgl.glfw.GLFW.GLFW_TRUE; +import static org.lwjgl.glfw.GLFW.GLFW_VISIBLE; +import static org.lwjgl.glfw.GLFW.glfwCreateWindow; +import static org.lwjgl.glfw.GLFW.glfwDefaultWindowHints; +import static org.lwjgl.glfw.GLFW.glfwDestroyWindow; +import static org.lwjgl.glfw.GLFW.glfwGetPrimaryMonitor; +import static org.lwjgl.glfw.GLFW.glfwGetVideoMode; +import static org.lwjgl.glfw.GLFW.glfwInit; +import static org.lwjgl.glfw.GLFW.glfwMakeContextCurrent; +import static org.lwjgl.glfw.GLFW.glfwPollEvents; +import static org.lwjgl.glfw.GLFW.glfwSetErrorCallback; +import static org.lwjgl.glfw.GLFW.glfwSetWindowFocusCallback; +import static org.lwjgl.glfw.GLFW.glfwSetWindowIcon; +import static org.lwjgl.glfw.GLFW.glfwSetWindowPos; +import static org.lwjgl.glfw.GLFW.glfwSetWindowSizeCallback; +import static org.lwjgl.glfw.GLFW.glfwSetWindowTitle; +import static org.lwjgl.glfw.GLFW.glfwShowWindow; +import static org.lwjgl.glfw.GLFW.glfwSwapBuffers; +import static org.lwjgl.glfw.GLFW.glfwSwapInterval; +import static org.lwjgl.glfw.GLFW.glfwWindowHint; +import static org.lwjgl.glfw.GLFW.glfwWindowShouldClose; import static org.lwjgl.opengl.GL11.GL_FALSE; -import static org.lwjgl.opengl.GL11.GL_TRUE; import static org.lwjgl.system.MemoryUtil.NULL; /** @@ -67,15 +117,10 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { private static final Logger LOGGER = Logger.getLogger(LwjglWindow.class.getName()); - protected AtomicBoolean needClose = new AtomicBoolean(false); + protected final AtomicBoolean needClose = new AtomicBoolean(false); protected final AtomicBoolean needRestart = new AtomicBoolean(false); - protected boolean wasActive = false; - protected boolean autoFlush = true; - protected boolean allowSwapBuffers = false; - private long window = NULL; + private final JmeContext.Type type; - private int frameRateLimit = -1; - private double frameSleepTime; private GLFWErrorCallback errorCallback; private GLFWWindowSizeCallback windowSizeCallback; @@ -83,6 +128,14 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { private Thread mainThread; + private double frameSleepTime; + private long window = NULL; + private int frameRateLimit = -1; + + protected boolean wasActive = false; + protected boolean autoFlush = true; + protected boolean allowSwapBuffers = false; + public LwjglWindow(final JmeContext.Type type) { if (!JmeContext.Type.Display.equals(type) && !JmeContext.Type.OffscreenSurface.equals(type) && !JmeContext.Type.Canvas.equals(type)) { throw new IllegalArgumentException("Unsupported type '" + type.name() + "' provided"); @@ -171,7 +224,7 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { glfwWindowHint(GLFW_VISIBLE, GL_FALSE); glfwWindowHint(GLFW_RESIZABLE, settings.isResizable() ? GLFW_TRUE : GLFW_FALSE); - //glfwWindowHint(GLFW_DOUBLE_BUFFER, GLFW_TRUE); + glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE); glfwWindowHint(GLFW_DEPTH_BITS, settings.getDepthBits()); glfwWindowHint(GLFW_STENCIL_BITS, settings.getStencilBits()); glfwWindowHint(GLFW_SAMPLES, settings.getSamples()); @@ -271,12 +324,91 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { glfwSwapInterval(0); } + final Object[] icons = settings.getIcons(); + + if (icons != null) { + + final GLFWImage[] images = imagesToGLFWImages(icons); + final GLFWImage.Buffer iconSet = GLFWImage.malloc(images.length); + + for (int i = images.length - 1; i >= 0; i--) { + final GLFWImage image = images[i]; + iconSet.put(i, image); + } + + glfwSetWindowIcon(window, iconSet); + + iconSet.free(); + + for (final GLFWImage image : images) { + image.free(); + } + } glfwShowWindow(window); allowSwapBuffers = settings.isSwapBuffers(); + } - // TODO: When GLFW 3.2 is released and included in LWJGL 3.x then we should hopefully be able to set the window icon. + /** + * Convert array of images to array of {@link GLFWImage}. + */ + private GLFWImage[] imagesToGLFWImages(final Object[] images) { + + final GLFWImage[] out = new GLFWImage[images.length]; + + for (int i = 0; i < images.length; i++) { + final BufferedImage image = (BufferedImage) images[i]; + out[i] = imageToGLFWImage(image); + } + + return out; + } + + /** + * Convert the {@link BufferedImage} to the {@link GLFWImage}. + */ + private GLFWImage imageToGLFWImage(BufferedImage image) { + + if (image.getType() != BufferedImage.TYPE_INT_ARGB_PRE) { + + final BufferedImage convertedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE); + final Graphics2D graphics = convertedImage.createGraphics(); + + final int targetWidth = image.getWidth(); + final int targetHeight = image.getHeight(); + + graphics.drawImage(image, 0, 0, targetWidth, targetHeight, null); + graphics.dispose(); + + image = convertedImage; + } + + final IntBuffer w = BufferUtils.createIntBuffer(1); + final IntBuffer h = BufferUtils.createIntBuffer(1); + final IntBuffer comp = BufferUtils.createIntBuffer(1); + + final ByteArrayOutputStream bout = new ByteArrayOutputStream(4096); + try { + ImageIO.write(image, "png", bout); + } catch (final IOException e) { + throw new RuntimeException(e); + } + + final byte[] bytes = bout.toByteArray(); + + final ByteBuffer buffer = BufferUtils.createByteBuffer(bytes.length); + buffer.put(bytes); + buffer.flip(); + + final ByteBuffer pixels = STBImage.stbi_load_from_memory(buffer, h, w, comp, 4); + + BufferUtils.destroyDirectBuffer(buffer); + + final GLFWImage result = GLFWImage.create(); + result.set(w.get(0), h.get(0), pixels); + + return result; } /** @@ -307,7 +439,7 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { glfwDestroyWindow(window); window = NULL; } - } catch (Exception ex) { + } catch (final Exception ex) { listener.handleError("Failed to destroy context", ex); } }