/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* 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 com.badlogic.gdx.backends.jglfw;
import static com.badlogic.jglfw.Glfw.*;
import java.awt.Toolkit;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Graphics;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Cursor;
import com.badlogic.gdx.graphics.Cursor.SystemCursor;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.glutils.GLVersion;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.jglfw.GlfwVideoMode;
import com.badlogic.jglfw.gl.GL;
/** An implementation of the {@link Graphics} interface based on GLFW.
* @author Nathan Sweet */
public class JglfwGraphics implements Graphics {
static final boolean isMac = System.getProperty("os.name").contains("OS X");
static final boolean isWindows = System.getProperty("os.name").contains("Windows");
static final boolean isLinux = System.getProperty("os.name").contains("Linux");
static GLVersion glVersion;
long window;
private boolean fullscreen;
private long fullscreenMonitor;
private String title;
private boolean resizable, undecorated;
private BufferFormat bufferFormat;
private boolean vSync;
int x, y, width, height;
private boolean visible;
private Color initialBackgroundColor;
private volatile boolean isContinuous = true, renderRequested;
volatile boolean foreground, minimized;
private long frameId = -1;
private float deltaTime;
private long frameStart, lastTime = -1;
private int frames, fps;
private JglfwGL20 gl20;
public JglfwGraphics (JglfwApplicationConfiguration config) {
// Store values from config.
bufferFormat = new BufferFormat(config.r, config.g, config.b, config.a, config.depth, config.stencil, config.samples, false);
title = config.title;
resizable = config.resizable;
undecorated = config.undecorated;
x = config.x;
y = config.y;
vSync = config.vSync;
initialBackgroundColor = config.initialBackgroundColor;
if (config.fullscreenMonitorIndex != -1) { // Use monitor specified in config if it is valid.
long[] monitors = glfwGetMonitors();
if (config.fullscreenMonitorIndex < monitors.length) fullscreenMonitor = monitors[config.fullscreenMonitorIndex];
}
// Create window.
if (!createWindow(config.width, config.height, config.fullscreen)) {
throw new GdxRuntimeException("Unable to create window: " + config.width + "x" + config.height + ", fullscreen: "
+ config.fullscreen);
}
// Create GL.
String versionString = GL.glGetString(GL20.GL_VERSION);
String vendorString = GL.glGetString(GL20.GL_VENDOR);
String rendererString = GL.glGetString(GL20.GL_RENDERER);
glVersion = new GLVersion(Application.ApplicationType.Desktop, versionString, vendorString, rendererString);
if (glVersion.getMajorVersion() <= 1)
throw new GdxRuntimeException("OpenGL 2.0 or higher with the FBO extension is required. OpenGL version: " + glVersion.getMajorVersion() + ":" + glVersion.getMinorVersion());
if (glVersion.getMajorVersion() == 2) {
if (!supportsExtension("GL_EXT_framebuffer_object") && !supportsExtension("GL_ARB_framebuffer_object")) {
throw new GdxRuntimeException("OpenGL 2.0 or higher with the FBO extension is required. OpenGL version: " + glVersion.getMajorVersion() + ":" + glVersion.getMinorVersion()
+ ", FBO extension: false");
}
}
gl20 = new JglfwGL20();
Gdx.gl = gl20;
Gdx.gl20 = gl20;
if (!config.hidden) show();
}
private boolean createWindow (int width, int height, boolean fullscreen) {
if (fullscreen && fullscreenMonitor == 0) fullscreenMonitor = getWindowMonitor();
glfwWindowHint(GLFW_VISIBLE, 0);
glfwWindowHint(GLFW_RESIZABLE, resizable ? 1 : 0);
glfwWindowHint(GLFW_UNDECORATED, undecorated ? 1 : 0);
glfwWindowHint(GLFW_RED_BITS, bufferFormat.r);
glfwWindowHint(GLFW_GREEN_BITS, bufferFormat.g);
glfwWindowHint(GLFW_BLUE_BITS, bufferFormat.b);
glfwWindowHint(GLFW_ALPHA_BITS, bufferFormat.a);
glfwWindowHint(GLFW_DEPTH_BITS, bufferFormat.depth);
glfwWindowHint(GLFW_STENCIL_BITS, bufferFormat.stencil);
glfwWindowHint(GLFW_SAMPLES, bufferFormat.samples);
boolean mouseCaptured = window != 0 && glfwGetInputMode(window, GLFW_CURSOR_MODE) == GLFW_CURSOR_CAPTURED;
long oldWindow = window;
long newWindow = glfwCreateWindow(width + (fullscreen ? 0 : -1), height, title, fullscreen ? fullscreenMonitor : 0, oldWindow);
if (newWindow == 0) return false;
if (oldWindow != 0) glfwDestroyWindow(oldWindow);
window = newWindow;
this.width = Math.max(1, width);
this.height = Math.max(1, height);
this.fullscreen = fullscreen;
if (!fullscreen) {
if (x == -1 || y == -1) {
DisplayMode mode = getDisplayMode();
x = (mode.width - width) / 2;
y = (mode.height - height) / 2;
}
glfwSetWindowPos(window, x, y);
}
if (!mouseCaptured) glfwSetInputMode(window, GLFW_CURSOR_MODE, GLFW_CURSOR_NORMAL); // Prevent fullscreen from taking mouse.
glfwSetWindowSize(window, width, height);
glfwMakeContextCurrent(window);
setVSync(vSync);
if (visible) glfwShowWindow(window);
return true;
}
void frameStart (long time) {
if (lastTime == -1) lastTime = time;
deltaTime = (time - lastTime) / 1000000000.0f;
lastTime = time;
if (time - frameStart >= 1000000000) {
fps = frames;
frames = 0;
frameStart = time;
}
frames++;
frameId++;
}
void sizeChanged (int width, int height) {
if (isMac) {
glfwShowWindow(window); // This is required to refresh the NSOpenGLContext on OSX!
}
width = Math.max(1, width);
height = Math.max(1, height);
this.width = width;
this.height = height;
Gdx.gl.glViewport(0, 0, width, height);
ApplicationListener listener = Gdx.app.getApplicationListener();
if (listener != null) listener.resize(width, height);
requestRendering();
}
void positionChanged (int x, int y) {
this.x = x;
this.y = y;
}
public boolean isGL20Available () {
return gl20 != null;
}
public GL20 getGL20 () {
return gl20;
}
public int getWidth () {
return width;
}
public int getHeight () {
return height;
}
@Override
public int getBackBufferWidth () {
return width;
}
@Override
public int getBackBufferHeight () {
return height;
}
public long getFrameId () {
return frameId;
}
public float getDeltaTime () {
return deltaTime;
}
public float getRawDeltaTime () {
return deltaTime;
}
public int getFramesPerSecond () {
return fps;
}
public GraphicsType getType () {
return GraphicsType.JGLFW;
}
public GLVersion getGLVersion () {
return glVersion;
}
public float getPpiX () {
// return getWidth() / (glfwGetMonitorPhysicalWidth(getWindowMonitor()) * 0.03937f); // mm to inches
return Toolkit.getDefaultToolkit().getScreenResolution();
}
public float getPpiY () {
// return getHeight() / (glfwGetMonitorPhysicalHeight(getWindowMonitor()) * 0.03937f); // mm to inches
return Toolkit.getDefaultToolkit().getScreenResolution();
}
public float getPpcX () {
// return getWidth() / (glfwGetMonitorPhysicalWidth(getWindowMonitor()) / 10); // mm to cm
return Toolkit.getDefaultToolkit().getScreenResolution() / 2.54f;
}
public float getPpcY () {
// return getHeight() / (glfwGetMonitorPhysicalHeight(getWindowMonitor()) / 10); // mm to cm
return Toolkit.getDefaultToolkit().getScreenResolution() / 2.54f;
}
public float getDensity () {
// long monitor = getWindowMonitor();
// float mmWidth = glfwGetMonitorPhysicalWidth(monitor);
// float mmHeight = glfwGetMonitorPhysicalHeight(monitor);
// float inches = (float)Math.sqrt(mmWidth * mmWidth + mmHeight * mmHeight) * 0.03937f; // mm to inches
// float pixelWidth = getWidth();
// float pixelHeight = getHeight();
// float pixels = (float)Math.sqrt(pixelWidth * pixelWidth + pixelHeight * pixelHeight);
// float diagonalPpi = pixels / inches;
// return diagonalPpi / 160f;
return Toolkit.getDefaultToolkit().getScreenResolution() / 160f;
}
public boolean supportsDisplayModeChange () {
return true;
}
@Override
public Monitor getPrimaryMonitor () {
return new JglfwMonitor(0, 0, "Primary Monitor");
}
@Override
public Monitor getMonitor () {
return getPrimaryMonitor();
}
@Override
public Monitor[] getMonitors () {
return new Monitor[] { getPrimaryMonitor() };
}
@Override
public DisplayMode[] getDisplayModes (Monitor monitor) {
return getDisplayModes();
}
@Override
public DisplayMode getDisplayMode (Monitor monitor) {
return getDisplayMode();
}
private long getWindowMonitor () {
if (window != 0) {
long monitor = glfwGetWindowMonitor(window);
if (monitor != 0) return monitor;
}
return glfwGetPrimaryMonitor();
}
public DisplayMode[] getDisplayModes () {
Array<DisplayMode> modes = new Array();
for (GlfwVideoMode mode : glfwGetVideoModes(getWindowMonitor()))
modes.add(new JglfwDisplayMode(mode.width, mode.height, 0, mode.redBits + mode.greenBits + mode.blueBits));
return modes.toArray(DisplayMode.class);
}
public DisplayMode getDisplayMode () {
GlfwVideoMode mode = glfwGetVideoMode(getWindowMonitor());
return new JglfwDisplayMode(mode.width, mode.height, 0, mode.redBits + mode.greenBits + mode.blueBits);
}
public boolean setFullscreenMode (DisplayMode displayMode) {
bufferFormat = new BufferFormat( //
displayMode.bitsPerPixel == 16 ? 5 : 8, //
displayMode.bitsPerPixel == 16 ? 6 : 8, //
displayMode.bitsPerPixel == 16 ? 6 : 8, //
bufferFormat.a, bufferFormat.depth, bufferFormat.stencil, bufferFormat.samples, false);
boolean success = createWindow(displayMode.width, displayMode.height, fullscreen);
if (success && fullscreen) sizeChanged(displayMode.width, displayMode.height);
return success;
}
public boolean setWindowedMode (int width, int height) {
boolean fullscreen = false;
if (fullscreen || this.fullscreen) {
boolean success = createWindow(width, height, fullscreen);
if (success && fullscreen) sizeChanged(width, height);
return success;
}
glfwSetWindowSize(window, width, height);
return true;
}
public void setTitle (String title) {
if (title == null) title = "";
glfwSetWindowTitle(window, title);
this.title = title;
}
/**
* Note: GLFW requires that the window be recreated for this change to take effect.
*/
@Override
public void setUndecorated (boolean undecorated) {
this.undecorated = undecorated;
}
/**
* Note: GLFW requires that the window be recreated for this change to take effect.
*/
@Override
public void setResizable (boolean resizable) {
this.resizable = resizable;
}
public void setVSync (boolean vsync) {
this.vSync = vsync;
glfwSwapInterval(vsync ? 1 : 0);
}
public BufferFormat getBufferFormat () {
return bufferFormat;
}
public boolean supportsExtension (String extension) {
return glfwExtensionSupported(extension);
}
public void setContinuousRendering (boolean isContinuous) {
this.isContinuous = isContinuous;
}
public boolean isContinuousRendering () {
return isContinuous;
}
public void requestRendering () {
renderRequested = true;
}
public boolean isFullscreen () {
return fullscreen;
}
/** Returns the JGLFW window handle. Note this should not be stored externally as it may change if the window is recreated to
* enter/exit fullscreen. */
public long getWindow () {
return window;
}
public int getX () {
return x;
}
public int getY () {
return y;
}
public void setPosition (int x, int y) {
glfwSetWindowPos(window, x, y);
}
public void hide () {
visible = false;
glfwHideWindow(window);
}
public void show () {
visible = true;
glfwShowWindow(window);
Gdx.gl.glClearColor(initialBackgroundColor.r, initialBackgroundColor.g, initialBackgroundColor.b, initialBackgroundColor.a);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
}
public boolean isHidden () {
return !visible;
}
public boolean isMinimized () {
return minimized;
}
public boolean isForeground () {
return foreground;
}
public void minimize () {
glfwIconifyWindow(window);
}
public void restore () {
glfwRestoreWindow(window);
}
boolean shouldRender () {
try {
return renderRequested || isContinuous;
} finally {
renderRequested = false;
}
}
@Override
public boolean isGL30Available () {
return false;
}
@Override
public GL30 getGL30 () {
return null;
}
@Override
public Cursor newCursor (Pixmap pixmap, int xHotspot, int yHotspot) {
return null;
}
@Override
public void setCursor (Cursor cursor) {
}
@Override
public void setSystemCursor (SystemCursor systemCursor) {
}
static class JglfwDisplayMode extends DisplayMode {
protected JglfwDisplayMode (int width, int height, int refreshRate, int bitsPerPixel) {
super(width, height, refreshRate, bitsPerPixel);
}
}
static class JglfwMonitor extends Monitor {
public JglfwMonitor (int virtualX, int virtualY, String name) {
super(virtualX, virtualY, name);
}
}
}