0

I am quite new to GLFW and OpenGL in general, and I'm working on a small model renderer. I am currently working on the inputs, and I am facing an issue with how GLFW handles inputs, let me explain : Every tutorials are telling to use glfwGetKey and an "if forest" to see if such and such key has been pressed. The issue I've got with that is that it might become slow if I map a lot of keys, plus it's ugly. So I use function pointers tables and glfwSetKeyCallback in order to speed that up and have a cleaner code. The issue I've got is I'm facing what looks like a race condition, the camera seems to stutter. I'm using a delta time computed on each frame in order to have constant speed. From what I could see, it seems like the key callback function is called every once in a while and not once on each frame when a key is repeated... I am using the latest version of glfw3 from their github, I swap the buffer at the beginning of each loop and use glfwPollEvents() at the end.

My question is the following : is there a way to synchronize the glfwPollEvents call and the rendering in order to avoid the stuttering and the deltatime difference between rendering loop and the callback function ? Thanks in advance for your help !

Tab
  • 81
  • 1
  • 11
  • How do you presume that "if forest" would cause you any performance trouble? Did you actually measure it? Yes it looks ugly and that it's hard to maintain is the valid reason to avoid it. But performance is not a reason. – datenwolf Apr 13 '16 at 08:22
  • Indeed, the performance difference won't be huge with modern CPU, but imagine you've got 1000 if...else if statements and the current case is the last one, it means your program will check every 999 other possibilities before actually doing someting, vs a simple function call in the case of the function pointer table. Also, as you said it's ugly and difficult to maintain, so "if forests" are to avoid at any cost performance cost or not IMHO. – Tab Apr 13 '16 at 10:08
  • There is a partial answer there : http://stackoverflow.com/questions/6805026/is-switch-faster-than-if Although I don't really know how compilers interpret if else if forests, I reasonably assume that tons of conditional checks would be logically slower than a simple function pointers table... – Tab Apr 13 '16 at 10:34

2 Answers2

2

Generally speaking, the way that you should be handling input is to keep a list of keys, and record their last input state.

struct key_event {
    int key, code, action, modifiers;
    std::chrono::steady_clock::time_point time_of_event;
}

std::map<int, bool> keys;
std::queue<key_event> unhandled_keys;
void handle_key(GLFWwindow* window, int key, int code, int action, int modifiers) {
    unhandled_keys.emplace_back(key, code, action, modifiers, std::chrono::steady_clock::now());
}

Then, in the render loop (or you can separate it into a different loop if you're confident with your multithreading + synchronization abilities) you can write code like this:

float now = glfwGetTime();
static float last_update = now;
float delta_time = now - last_update;
last_update = now;
handle_input(delta_time);

Where handle_input would look like this:

float external_position[2];
std::map<int, std::function<void(/*args*/)>> key_functions;
void handle_input(float delta_time) {
    //Anything that should happen "when the users presses the key" should happen here
    while(!unhandled_keys.is_empty()) {
        key_event event = unhandled_keys.front();
        unhandled_keys.pop();
        key_functions[event.key](/*args*/);
        bool pressed = event.action == GLFW_PRESS || event.action == GLFW_REPEAT;
        keys[event.key] = pressed;
    }
    //Anything that should happen "while the key is held down" should happen here.
    float movement[2] = {0,0};
    if(keys[GLFW_KEY_W]) movement[0] += delta_time;
    if(keys[GLFW_KEY_S]) movement[0] -= delta_time;
    if(keys[GLFW_KEY_A]) movement[1] -= delta_time;
    if(keys[GLFW_KEY_D]) movement[1] += delta_time;
    external_position[0] += movement[0];
    external_position[1] += movement[1];
}

EDIT: I've added logic to handle "on press" / "on release" type functions. So if, for example, this code were in the renderer:

key_functions[GLFW_KEY_SPACE] = [&renderer] {renderer.pause();};

Then pressing the [Space] key would pause the renderer.

Xirema
  • 19,889
  • 4
  • 32
  • 68
  • Hi and thank you very much for your answer ! The issue with this code is that there is what I would call a "if forest" meaning that it checks each keys once... What I am looking for is a way to know which key is pressed and get the keycode (which glfwSetKeyCallback allows you to do), so I can call my key's callback functions like so : key[keycode][action].function(keycode, action, mods, key[keycode][action].arg); Which is a lot faster and cleaner IMO... The issue is that when using glfwSetKeyCallback, I get synchronization issues... – Tab Apr 12 '16 at 18:25
  • @Tab The point I'm making is that the callback should be extremely lightweight. It's bad to have a callback function having to perform tasks that might take more than a few microseconds. I'll add some code to specifically handle "on press" type logic. – Xirema Apr 12 '16 at 18:33
  • @Tab I've updated the post with some extra information. – Xirema Apr 12 '16 at 18:46
  • Thank you very much for this answer, I guess I'll leave glfwSetKeyCallback alone (as it runs 60 times/s no matter what) for the moment and try to find an alternative solution, perhaps using an other library (SDL seems nice, although a bit "invasive") By the way, the synchronisation issue seems less severe when forcing ActiveSync, but I want to keep my framerate as high as possible for now (mainly for fun in fact) and being able to choose to throttle it or not later Thank you for your time, accepted the answer ! ;) – Tab Apr 12 '16 at 20:31
0

Okay so, I'll answer my own question (and leave @Xirema 's answer as valid as he was right)

I think I just understood why it was not synchronised between framerate and callback on repeat, it's because of the way the OS handles key repeat, it won't send 4000+ key repeat signals per seconds (as I get 4000+ fps) and will limit itself to 60 calls/s or so ! To correct this issue, I will indeed simply register the last pressed key 60 times/s in the callback function and execute the function outside of the GLFWkeyfun (in the main loop) once every frame, or find a way to normalize the movements to avoid stuttering !

Anyway, thanks again for taking time to answer @Xirema I hope my question can be usefull to someone, now, off to code the awesome ! (not really ;-) )

P.S : Knowing how many times the key repeat signal is sent, using a fixed deltatime value in the key_repeat callback functions can also do the trick !

P.P.S : Well, considering keyboard repeat can vary depending on the OS or user settings or even CPU power, the best solution is to have a separate delta-time for the GLFWkeyfun, this way you can send it to your callback functions and always get correct delta time for the key repeat and avoid all stuttering !

Tab
  • 81
  • 1
  • 11