Background Info:
I am using OpenGL and LWJGL 3 to draw some quads onto the screen. I need to know when the mouse is over a quad. When I render the quads to the screen, I use the OpenGL coordinates, ranging from -1 to 1 for both X and Y and with (0,0) at the center of the screen. When I get the mouse position I use
glfwSetCursorPosCallback();
which gives me the coordinates ranging from 0 to the width or height of the window and with (0,0) at the top left corner (below the title bar). I then take the mouse coordinate and calculate the OpenGL coordinates.
For example if my window size is (800, 600) and my mouse was at (200, 200) I would get (-0.5, 0.33) [since (400, 300) would map to (0, 0) in OpenGL's coordinates].
So here's my problem:
OpenGL includes the title bar in its coordinates, where as glfwSetCursorPosCallback();
does not. This means that if I render a vertex at (-0.5, 0.33) [like in my example] it renders at around (200, ~210).
As you can see, because the two coordinate systems cover different areas, its more difficult to switch between the coordinate systems.
I have searched for ways to exclude the title bar from OpenGL's coordinates, to completely get rid of the title bar and to get the height of the title bar (so I can include it in my calculations and make the correct adjustments). I haven't been able to figure out how to do any of these, so I'm looking for a way to do so, or a different method that will resolve my problem.
EDIT 1: Adding Code
@Nicol Bolas informed me that this is not how OpenGL normally works so there must be something causing this in my code. I believe I've provided the parts of my code that would be responsible for my problem:
Here is my Renderer class [I am using the drawQuad() method]
Note: I am not currently using the view, model, or projection matrices in my shaders.
public class Renderer {
private VertexArrayObject vao;
private VertexBufferObject vbo;
private ShaderProgram shaderProgram;
private FloatBuffer vertices;
private int numVertices;
private boolean drawing;
//private Font font;
//private Font debugFont;
public void drawQuad(float x, float y, float width, float height, Color c) {
/* Calculate Vertex positions */
float x1 = x;
float y1 = y;
float x2 = x + width;
float y2 = y - height;
/* Calculate color */
float r = c.getRed();
float g = c.getGreen();
float b = c.getBlue();
/* Put data into buffer */
vertices.put(x1).put(y1).put(0.0f).put(r).put(g).put(b);
vertices.put(x1).put(y2).put(0.0f).put(r).put(g).put(b);
vertices.put(x2).put(y2).put(0.0f).put(r).put(g).put(b);
vertices.put(x2).put(y1).put(0.0f).put(r).put(g).put(b);
/* We drawed X vertices */
numVertices += 4;
}
// Initialize renderer
public void init(){
// Set up shader programs
setupShaderProgram();
// Enable blending (?????)
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
// Clears drawing area
public void clear() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
// Begin rendering
public void begin() {
if (drawing) throw new IllegalStateException("Renderer is already drawing.");
drawing = true;
numVertices = 0;
}
// End rendering
public void end() {
if (!drawing) throw new IllegalStateException("Renderer is not drawing.");
drawing = false;
flush();
}
// Flushes data to GPU to get rendered
public void flush() {
if (numVertices > 0) {
vertices.flip();
if (vao != null) vao.bind();
else vbo.bind(GL_ARRAY_BUFFER);
specifyVertexAttributes();
}
shaderProgram.use();
// Upload the new vertex data
vbo.bind(GL_ARRAY_BUFFER);
vbo.uploadSubData(GL_ARRAY_BUFFER, 0, vertices);
// Draw batch
glDrawArrays(GL_QUADS, 0, numVertices);
// Clear vertex data for next batch
vertices.clear();
numVertices = 0;
}
private void setupShaderProgram() {
// Generate VertexArrayObject
if (Game.is32Supported()) {
vao = new VertexArrayObject();
vao.bind();
} else {
throw new RuntimeException("OpenGL 3.2 not supported.");
}
// Generate VertexBufferObject
vbo = new VertexBufferObject();
vbo.bind(GL_ARRAY_BUFFER);
// Create FloatBuffer
vertices = MemoryUtil.memAllocFloat(4096);
// Upload null data to allocate storage for the VBO
long size = vertices.capacity() * Float.BYTES;
vbo.uploadData(GL_ARRAY_BUFFER, size, GL_DYNAMIC_DRAW);
// Initialize variables
numVertices = 0;
drawing = false;
// Load Shaders:
Shader vertexShader, fragmentShader;
if (Game.is32Supported()) {
vertexShader = Shader.loadShader(GL_VERTEX_SHADER, "res/shaders/vshader.vert");
fragmentShader = Shader.loadShader(GL_FRAGMENT_SHADER, "res/shaders/fshader.frag");
} else {
throw new RuntimeException("OpenGL 3.2 not supported.");
}
// Create ShaderProgram
shaderProgram = new ShaderProgram();
shaderProgram.attachShader(vertexShader);
shaderProgram.attachShader(fragmentShader);
if (Game.is32Supported()) {
shaderProgram.bindFragmentDataLocation(0, "fragColor");
}
shaderProgram.link();
shaderProgram.use();
// Delete linked shaders
vertexShader.delete();
fragmentShader.delete();
// Get width & height of framebuffer
long window = GLFW.glfwGetCurrentContext();
int width, height;
try (MemoryStack stack = MemoryStack.stackPush()) {
IntBuffer widthBuffer = stack.mallocInt(1);
IntBuffer heightBuffer = stack.mallocInt(1);
GLFW.glfwGetFramebufferSize(window, widthBuffer, heightBuffer);
width = widthBuffer.get();
height = heightBuffer.get();
}
// Specify vertex pointers
specifyVertexAttributes();
// Set Model Matrix to identity matrix
Matrix4f model = new Matrix4f();
int uniModel = shaderProgram.getUniformLocation("model");
shaderProgram.setUniform(uniModel, model);
// Set View Matrix to identity matrix
Matrix4f view = new Matrix4f();
int uniView = shaderProgram.getUniformLocation("view");
shaderProgram.setUniform(uniView, view);
// Set Projection Matrix to an orthographic projection
Matrix4f projection = Matrix4f.orthographic(0f, width, 0f, height, -1f, 1f);
int uniProjection = shaderProgram.getUniformLocation("projection");
shaderProgram.setUniform(uniProjection, projection);
}
// Specifies the vertex shader pointers (attributes)
private void specifyVertexAttributes() {
int posAttrib = shaderProgram.getAttributeLocation("position");
shaderProgram.enableVertexAttribute(posAttrib);
shaderProgram.pointVertexAttribute(posAttrib, 3, 6 * Float.BYTES, 0);
int colAttrib = shaderProgram.getAttributeLocation("color");
shaderProgram.enableVertexAttribute(colAttrib);
shaderProgram.pointVertexAttribute(colAttrib, 3, 6 * Float.BYTES, 3 * Float.BYTES);
}
}
And here is my init() method that creates and sets up my window:
private void init() {
// Setup an error callback. The default implementation
// will print the error message in System.err.
GLFWErrorCallback.createPrint(System.err).set();
// Initialize GLFW. Most GLFW functions will not work before doing this.
if ( !glfwInit() )
throw new IllegalStateException("Unable to initialize GLFW");
// Configure GLFW
glfwDefaultWindowHints(); // optional, the current window hints are already the default
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // the window will stay hidden after creation
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // the window will be resizable
// ONLY ON MAC OSX (?)
//glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Tell GLFW to use OpenGL verison 3.x
//glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); // Tell GLFW to use OpenGL version x.2 (combined -> 3.2)
//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); // Should be forward compatible
// Create the window
window = glfwCreateWindow(WIDTH, HEIGHT, "Game_19_v0.0.1", NULL, NULL);
if ( window == NULL )
throw new RuntimeException("Failed to create the GLFW window");
// Setup a key callback. It will be called every time a key is pressed, repeated or released.
glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
if ( key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE )
glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop
});
// Get the thread stack and push a new frame
try ( MemoryStack stack = stackPush() ) {
IntBuffer pWidth = stack.mallocInt(1); // int*
IntBuffer pHeight = stack.mallocInt(1); // int*
// Get the window size passed to glfwCreateWindow
glfwGetWindowSize(window, pWidth, pHeight);
// Get the resolution of the primary monitor
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
// Center the window
glfwSetWindowPos(
window,
(vidmode.width() - pWidth.get(0)) / 2,
(vidmode.height() - pHeight.get(0)) / 2
);
} // the stack frame is popped automatically
// Make the OpenGL context current
glfwMakeContextCurrent(window);
// Enable v-sync
glfwSwapInterval(1);
// Make the window visible
glfwShowWindow(window);
// This line is critical for LWJGL's interoperation with GLFW's
// OpenGL context, or any context that is managed externally.
// LWJGL detects the context that is current in the current thread,
// creates the GLCapabilities instance and makes the OpenGL
// bindings available for use.
GL.createCapabilities();
// Input
glfwSetCursorPosCallback(window, cursorPosCallback = new MouseInput());
// Create renderer
renderer = new Renderer();
renderer.init();
// To Render:
buttonManager = new ButtonManager();
}
EDIT 2: Temporary Solution
I was able to use glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
to remove the entire border from the window, title bar included, which fixed the issue. Now however, I obviously don't have the options to close, minimize, etc., on my window, although I suppose I can program those in myself if necessary. Will update if I find out any other solutions.
GLFW functions typically work with the client area of a window (the inside window area not including titlebars, scrollbars, etc.) so glfwSetCursorPosCallback
is giving you the expected values. If your OpenGL framebuffer is for some reason rendering things behind the title bar (whether it's an improper setup or just a platform specific detail) you should still be able to get the title bar size using glfwGetWindowFrameSize:
IntBuffer pLeft = stack.mallocInt(1); // int*
IntBuffer pTop = stack.mallocInt(1); // int*
IntBuffer pRight = stack.mallocInt(1); // int*
IntBuffer pBottom = stack.mallocInt(1); // int*
// Get the window border sizes
glfwGetWindowFrameSize(window, pLeft, pTop, pRight, pBottom);
(Disclaimer: I'm just following the syntax from your code above since I'm only familiar with the C++ API.)
The size of the title bar will be stored in the top
variable and can then be added to whatever value you get from glfwSetCursorPosCallback
and glfwGetWindowSize
.
float adjustedYpos = ypos + top;
float adjustedHeight = height + top;
float normalizedY = adjustedYpos / adjustedHeight;
float openglY = normalizedY * -2.0f - 1.0f
This openglY
value should be the OpenGL [-1, 1] clip-space coordinate adjusted based on the title bar size.