LWJGL Forum

Programming => General Java Game Development => Topic started by: Lugen on July 06, 2015, 08:06:27

Title: GLFW KeyCallback Only certain keys seem to work
Post by: Lugen on July 06, 2015, 08:06:27
So this is a rather odd problem I'm having. I would guess that I'm missing something here.

So I'm currently following this tutorial (https://www.youtube.com/watch?v=527bR2JHSR0&index=9&list=PLlrATfBNZ98e5KBKGcL7ARy3DjstzI2TV) and I'm getting a strange problem with keyboard input. At first it appeared that no keys where detected but then after pressing randomly I found only a few where triggering the callback and even stranger, pressing these keys would for a brief moment enable a couple of other keys as well.

I'm pointing to the GLFWKeyCallback function through this class.
Code: [Select]
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWKeyCallback;

public class Input extends GLFWKeyCallback{

// Keycode sate list, size set according to available number of keys
public static boolean[] keys = new boolean[65536];

// Main key callback
// This functions needs to be wrapped in a class in order to be referenced
public void invoke(long window, int key, int scancode, int action, int mods) {
System.out.println(key);
keys[key] = action != GLFW.GLFW_RELEASE;
}

// Key down
public static boolean isKeyDown(int keycode) {
return keys[keycode];
}
}

Then set the callback like this during initialization of the window.
Code: [Select]
if (glfwInit() == GL_FALSE) {
return;
}

glfwWindowHint(GLFW_RESIZABLE, GL_TRUE);

window = glfwCreateWindow(width, height, "Tutorial", NULL, NULL);
if (window == NULL) {
System.err.println("Could not create GLFW window.");
return;
}

ByteBuffer videoMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwSetWindowPos(window, (GLFWvidmode.width(videoMode) - width) / 2,
(GLFWvidmode.height(videoMode) - height) / 2);

glfwSetKeyCallback(window, new Input());

glfwMakeContextCurrent(window);
glfwShowWindow(window);
GLContext.createFromCurrent();

Then in my update function I simply run glfwPollEvents(). Ups is at 60.

I've also tried this code (https://github.com/sriharshachilakapati/LWJGL-Tutorial-Series/blob/1ec0189920c64afa5fe877b5d85a1a4d71a208ad/src/com/shc/tutorials/lwjgl/Game.java) and it gives the same result.

Any clues whatsoever are very much appreciated!
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: spasi on July 06, 2015, 08:19:08
The part of your code that polls events and tests for key state is missing. Could you please post a fully working example?

From the above code, I see two issues: a) you're not holding a reference to "new Input()", the program will crash if you don't and b) the boolean[] keys array is too big; you don't need more than GLFW_KEY_LAST+1 elements.
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: Lugen on July 06, 2015, 09:25:57
The part of your code that polls events and tests for key state is missing. Could you please post a fully working example?

From the above code, I see two issues: a) you're not holding a reference to "new Input()", the program will crash if you don't and b) the boolean[] keys array is too big; you don't need more than GLFW_KEY_LAST+1 elements.

Thanks for the fast reply.
I mentioned the poll test passingly as it's just this code.
Code: [Select]
private void update() {
glfwPollEvents();
level.update();
}

Is this what you mean by holding a reference to new Input()?
"input" in this case is a private variable in the main class declaration.
Code: [Select]
input = new Input();
glfwSetKeyCallback(window, input);

Heck, I'll just post the entire Main-class in case that helps.
Code: [Select]
package tutorial;

// Imports static methods
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.*;
import static org.lwjgl.system.MemoryUtil.*;

import java.nio.ByteBuffer;

import org.lwjgl.glfw.GLFWvidmode;
import org.lwjgl.opengl.GLContext;

import tutorial.graphic.Shader;
import tutorial.input.Input;
import tutorial.level.Level;
import tutorial.math.Mat4;

public class Main implements Runnable {

private int width = 1280;
private int height = 720;

private Thread thread;
private boolean bRunning = false;

// There are no objects in C, only identifiers
private long window;
private Input input;

// Level instance
private Level level;

public void start() {
bRunning = true;
thread = new Thread(this, "Game");
thread.start();
}

// Initiate opengl, needs to be on same thread as render()
public void init() {
// Handle if initialization failed
if (glfwInit() == GL_FALSE) {
return;
}

// Create window
glfwWindowHint(GLFW_RESIZABLE, GL_TRUE);

// Will return id for window, perhaps memory adress
window = glfwCreateWindow(width, height, "Tutorial", NULL, NULL);
if (window == NULL) {
System.err.println("Could not create GLFW window.");
return;
}
// Set window position
ByteBuffer videoMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwSetWindowPos(window, (GLFWvidmode.width(videoMode) - width) / 2,
(GLFWvidmode.height(videoMode) - height) / 2);

// Initiate key callback with our Input class
input = new Input();
glfwSetKeyCallback(window, input);

glfwMakeContextCurrent(window);
glfwShowWindow(window);
// Make sure Opengl is running under the current window context
GLContext.createFromCurrent();

// OpenGl stuff
glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
glEnable(GL_DEPTH_TEST);
glActiveTexture(GL_TEXTURE1);

// Print OpenGL info
System.out.println("OpenGl: " + glGetString(GL_VERSION));

// Load shaders
Shader.loadAll();

// Projection matrix
Mat4 matrixProjection = Mat4.orthographic(-10, 10, -10 * 9 / 16, 10 * 9 / 16, -1, 1);
Shader.BG.setUniformMat4("projection", matrixProjection);
Shader.BG.setUniformInt("tex", 1);

Shader.BIRD.setUniformMat4("projection", matrixProjection);
Shader.BIRD.setUniformInt("tex", 1);

// Create level
level = new Level();
}

public void run() {
init();

// Timer
long timer = System.currentTimeMillis();
long timeLast = System.nanoTime();
// Not time delta for checking time between frames
double timeDelta = 0;
double timeUpdate = 1000000000.0 / 60.0;

int countUpdate = 0;
int countRender = 0;

while (bRunning) {
long timeNow = System.nanoTime();
timeDelta += (timeNow - timeLast) / timeUpdate;
timeLast = timeNow;

// Every time delta reaches 1 do update
if (timeDelta >= 1.0) {
update();
countUpdate++;
timeDelta--;
}

render();
countRender++;

// Reset counters when one second has passed
if (System.currentTimeMillis() - timer > 1000) {
timer += 1000;
System.out.println(countUpdate + " ups, " + countRender + " fps");
countUpdate = 0;
countRender = 0;
}

// Window close event
if (glfwWindowShouldClose(window) == GL_TRUE)
bRunning = false;
}

glfwDestroyWindow(window);
glfwTerminate();
}

// Update
private void update() {
glfwPollEvents();
level.update();
}

// Render
private void render() {
// Clear previous frame
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// Draw new things
level.render();

int error = glGetError();
if (error != GL_NO_ERROR)
System.out.println(error);

glfwSwapBuffers(window);
}

public static void main(String[] args) {
new Main().start();
}
}
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: spasi on July 06, 2015, 09:37:23
Key input works fine for me with the above code. Could you describe the sequence of key presses/releases you're doing and the expected vs actual outcome?
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: Lugen on July 06, 2015, 11:35:28
Key input works fine for me with the above code. Could you describe the sequence of key presses/releases you're doing and the expected vs actual outcome?
Interesting, might be worth to mention that I have a Swedish keyboard but that shouldn't make any difference right?
http://frontype.com/keyboarding/540px-Computer-keyboard-Sweden.svg.png

Right now I'm just printing the key id to console whenever the key callback is being called, to check which keys generate a response. The pattern seems to to be that only the buttons marked with red (see reference above) calls the callback and some other buttons (W , A , S, D, Enter, Backspace, + and perhaps more) works as well if pressed shortly after.

Am I being clear? To me it seems like there's some weird timing going on and/or polls being lost for some reason.

I also have controls set up for a player object to move around. The controls do actually work but only right after pressing those red buttons as mentioned.
Update method from the player class. Is executed after glfwPollEvents().
Code: [Select]
public void update() {
if (Input.keys[GLFW.GLFW_KEY_W])
position.y += 0.1f;
if (Input.keys[GLFW.GLFW_KEY_S])
position.y -= 0.1f;
if (Input.keys[GLFW.GLFW_KEY_A])
position.x -= 0.1f;
if (Input.keys[GLFW.GLFW_KEY_D])
position.x += 0.1f;
}
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: spasi on July 06, 2015, 15:29:51
The pattern seems to to be that only the buttons marked with red (see reference above) calls the callback and some other buttons (W , A , S, D, Enter, Backspace, + and perhaps more) works as well if pressed shortly after.

That sounds very weird. Could you try on another PC or a different keyboard?

To me it seems like there's some weird timing going on and/or polls being lost for some reason.

Does it help if you enable vsync? Call glfwSwapInterval(1) after glfwMakeContextCurrent(window).
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: Lugen on July 06, 2015, 17:54:29
The pattern seems to to be that only the buttons marked with red (see reference above) calls the callback and some other buttons (W , A , S, D, Enter, Backspace, + and perhaps more) works as well if pressed shortly after.

That sounds very weird. Could you try on another PC or a different keyboard?

Not at the moment sorry. It's weird indeed.

To me it seems like there's some weird timing going on and/or polls being lost for some reason.

Does it help if you enable vsync? Call glfwSwapInterval(1) after glfwMakeContextCurrent(window).

No difference. Seems like it was already enabled anyway.

Studying the console output the two "red keys" seem to pretty much enable any other key in the same way as mentioned. Also I noticed that pressing any other key while holding one of the "red keys" down stops their keycodes to get printed, so something is happening there. It's like those two buttons act like some kind of floodgate, blocking the rest of the keyboard.

Btw, doing input with the standard java libraries work fine. I haven't had any input problems with any linux applications before.
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: spasi on July 06, 2015, 18:22:13
Sounds like GLFW cannot handle your keyboard layout properly. Have you tried switching layouts from the linux keyboard settings? What Linux distribution are you using?
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: Lugen on July 06, 2015, 20:10:33
Sounds like GLFW cannot handle your keyboard layout properly. Have you tried switching layouts from the linux keyboard settings? What Linux distribution are you using?
Tried some of the English keyboard layouts (US, Canada, UK) but didn't seem to get any response at all.
I'm on Ubuntu 12.04, thinking of upgrading to 14.04 someday, just haven't bothered with it for the time being, just mentioning in case that's relevant. I also replaced the default desktop environment "Unity" with "Gnome Classic".
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: spasi on July 06, 2015, 20:18:52
Could you try the Events (https://github.com/LWJGL/lwjgl3/blob/master/modules/core/src/test/java/org/lwjgl/demo/glfw/Events.java) demo? Try pressing a few keys, what output are you getting?
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: Lugen on July 06, 2015, 21:59:51
Could you try the Events (https://github.com/LWJGL/lwjgl3/blob/master/modules/core/src/test/java/org/lwjgl/demo/glfw/Events.java) demo? Try pressing a few keys, what output are you getting?

Seems like it's working as it should. I can move the mouse cursor around and the console responds to any key presses with messages like this.
53.146: Window [0x7F5B80259F60] char mods h
53.146: Window [0x7F5B80259F60] char h


That's a positive sign. Will have a closer look at the code and see if I understand it. Anything particular with this setup I should take interest in?
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: spasi on July 07, 2015, 09:24:59
The demo simply prints the events received to stdout. This is the output I see when pressing the 'H' key:

Quote
28.007: Window [0xE9E850] key [GLFW_KEY_H - 35] was pressed
28.008: Window [0xE9E850] char mods h
28.008: Window [0xE9E850] char h
28.075: Window [0xE9E850] key [GLFW_KEY_H - 35] was released
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: spasi on July 07, 2015, 09:38:39
Looks like there is indeed an issue (https://github.com/glfw/glfw/issues/548) related to input method in the latest GLFW builds.
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: Lugen on July 07, 2015, 11:07:22
The demo simply prints the events received to stdout. This is the output I see when pressing the 'H' key:

Quote
28.007: Window [0xE9E850] key [GLFW_KEY_H - 35] was pressed
28.008: Window [0xE9E850] char mods h
28.008: Window [0xE9E850] char h
28.075: Window [0xE9E850] key [GLFW_KEY_H - 35] was released

With that in mind I tried the demo again and noticed the same button enabling pattern. Only the "red keys" gives me the GLFW printouts.
The key next to "Backspace".
2.141: Window [0x7FA3CC268440] key [GLFW_KEY_EQUAL - 21] was pressed
2.220: Window [0x7FA3CC268440] key [GLFW_KEY_EQUAL - 21] was released

The key next to "Enter".
6.255: Window [0x7FD27826DFE0] key [GLFW_KEY_RIGHT_BRACKET - 35] was pressed
6.415: Window [0x7FD27826DFE0] key [GLFW_KEY_RIGHT_BRACKET - 35] was released

Same pattern as before. Some keys get GLFW messages after pressing this key.
9.922: Window [0x7FD6082705C0] key [GLFW_KEY_EQUAL - 21] was pressed
10.034: Window [0x7FD6082705C0] key [GLFW_KEY_EQUAL - 21] was released
10.162: Window [0x7FD6082705C0] key [GLFW_KEY_H - 43] was pressed
10.274: Window [0x7FD6082705C0] key [GLFW_KEY_H - 43] was released

And others after pressing this one.
51.018: Window [0x7FFAA4280760] key [GLFW_KEY_RIGHT_BRACKET - 35] was pressed
51.122: Window [0x7FFAA4280760] key [GLFW_KEY_RIGHT_BRACKET - 35] was released
51.218: Window [0x7FFAA4280760] key [GLFW_KEY_S - 39] was pressed
51.290: Window [0x7FFAA4280760] key [GLFW_KEY_S - 39] was released

I also get this when the program starts, but I assume the error is just a test?
---- [ Error callback test ] ----
[LWJGL] GLFW_NOT_INITIALIZED error
   Description : The GLFW library is not initialized
   Stacktrace  :
      test.Events.main(Events.java:42)
---- [ Error callback done ] ----
GLFW initialized
Window opened.
0.210: Window [0x7FFAA4280760] moved to 1, 28
0.229: Window [0x7FFAA4280760] restored
0.230: Window [0x7FFAA4280760] refreshed
0.230: Window [0x7FFAA4280760] cursor entered
0.231: Window [0x7FFAA4280760] gained focus

Looks like there is indeed an issue (https://github.com/glfw/glfw/issues/548) related to input method in the latest GLFW builds.
That makes sense if the reason why the keys aren't working is because they are being interpreted as characters. Pressing those two special keys seem to temporarily "correct" them.
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: Lugen on July 31, 2015, 20:44:52
In case this info is useful. I recently switched to Ubuntu 14.04 and key input seems to work fine now. Still using the same version, 3.0.0a.
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: spasi on July 31, 2015, 21:11:56
The nightly build has a temporary fix for this. A better fix is coming to GLFW soon.
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: Lugen on August 03, 2015, 18:43:32
The nightly build has a temporary fix for this. A better fix is coming to GLFW soon.
Thanks for the info.

I may have another problem, unless it's just me not knowing what I'm doing.
So as before I've set up the GLFWKeyCallback method like this.

Code: [Select]
public class Keyboard extends GLFWKeyCallback {

private static boolean[] down = new boolean[GLFW.GLFW_KEY_LAST + 1];
private static boolean[] pressed = new boolean[GLFW.GLFW_KEY_LAST + 1];
private static boolean[] released = new boolean[GLFW.GLFW_KEY_LAST + 1];

public void invoke(long window, int key, int scancode, int action, int mods) {
System.out.println("PRESSED " + key);
down[key] = action != GLFW.GLFW_RELEASE;
pressed[key] = action == GLFW.GLFW_PRESS;
released[key] = action == GLFW.GLFW_RELEASE;
}
}

I call glfwPollEvents() every update which runs at 60 FPS but judging from the print to console it seems like invoke is only called about 20 times per second if I hold down a button constantly. Is there a way to control this or perhaps some good alternative way I can set up keyboard control without using the callback?
I'm trying to track when a button is pressed for the first time. Am I wrong to assume that is what "GLFW_PRESS" does, cause it seems to be doing that but pressed[key] doesn't get set to false until a second has passed.
Title: Re: GLFW KeyCallback Only certain keys seem to work
Post by: spasi on August 03, 2015, 20:30:07
The behavior you're seeing is normal and depends on the OS and the keyboard hardware/driver. It will become clear if you just print the callback events, instead of printing the current state of the down/pressed/released arrays:

- You first get a GLFW_PRESS event for the key.
- After some time (several hundred milliseconds at least), the OS starts sending repeat events and you get your first GLFW_REPEAT event.
- The GLFW_REPEAT events have lower frequency than your event loop (the number of times you call glfwPollEvents()).

I call glfwPollEvents() every update which runs at 60 FPS but judging from the print to console it seems like invoke is only called about 20 times per second if I hold down a button constantly. Is there a way to control this or perhaps some good alternative way I can set up keyboard control without using the callback?

Even if you could, it's bad practice. You shouldn't mess with the user's keyboard repeat rate, it's a personal preference.

You could implement your own event repeating though; start a timer/counter on GLFW_PRESS and fire an event every x frames/milliseconds, where x is the repeat rate you want. Stop on GLFW_RELEASE.

I'm trying to track when a button is pressed for the first time. Am I wrong to assume that is what "GLFW_PRESS" does, cause it seems to be doing that but pressed[key] doesn't get set to false until a second has passed.

Correct, GLFW_PRESS is fired only once, the moment the key is pressed. As I explained above, the behavior you're seeing is caused by the code you used, simply because the first GLFW_REPEAT event is delayed.