GLFW Mouse event lag with window drag

1.8k views Asked by At

I am trying to drag an undecorated window in GLFW but am encountering some event lag, even though I am using glfwWaitEvents()

I have a cursor position callback, and a simple loop:

// register a cursor position callback
glfwSetCursorPosCallback(win, cursor_pos_callback);

// then loop..
while(!glfwWindowShouldClose(win)) {
  glfwWaitEvents();
  ... some rendering...
  glfwSwapBuffers(win);
}

My cursor callback does some simple tracking of the deltas and updates window position.

cursor_pos_callback(GLFWwindow *win, double xpos, double ypos) {
  // figure out delta_x and delta_y based on cursor previous position
  int delta_x, delta_y;

  // update window position
  if (window_drag_active) {
     int x,y;
     glfwGetWindowPos(window, &x, &y);
     glfwSetWindowPos(window, x + delta_x, y + delta_y);
  }
}

Here is what the deltas look like when I'm dragging in a straight line

delta_x:  10    delta_y:   0    |    xpos:   649    ypos: 55
delta_x:   5    delta_y:  -1    |    xpos:   654    ypos: 54
delta_x:   5    delta_y:   3    |    xpos:   659    ypos: 57
delta_x:   5    delta_y:   2    |    xpos:   664    ypos: 59
delta_x:  -5    delta_y:  -2    |    xpos:   659    ypos: 57
delta_x:   4    delta_y:   0    |    xpos:   663    ypos: 57
delta_x:   2    delta_y:   0    |    xpos:   665    ypos: 57
delta_x:  -3    delta_y:  -3    |    xpos:   662    ypos: 54
delta_x:   2    delta_y:   1    |    xpos:   664    ypos: 55
delta_x:   2    delta_y:   0    |    xpos:   666    ypos: 55
delta_x:   3    delta_y:   2    |    xpos:   669    ypos: 57
delta_x:   1    delta_y:  -1    |    xpos:   670    ypos: 56
delta_x:   2    delta_y:  -1    |    xpos:   672    ypos: 55
delta_x:   7    delta_y:   3    |    xpos:   679    ypos: 58
delta_x:   2    delta_y:  -1    |    xpos:   681    ypos: 57
delta_x:  -2    delta_y:  -3    |    xpos:   679    ypos: 54
delta_x:   0    delta_y:  -2    |    xpos:   679    ypos: 52
delta_x:   3    delta_y:   3    |    xpos:   682    ypos: 55
delta_x:  -5    delta_y:  -3    |    xpos:   677    ypos: 52

The xpos increments as it should, then every so often it goes backwards (stale event?)

Maybe my window movement is not synced up with the cursor?

The result is the when I drag the window, it shakes violently and I can barely move it anywhere...


Update: I have also tried moving the glfwSetWindowPos logic into the main loop as well with no success—I still get the same shaking and stuttering.


Update: When I comment out the glfwSetWindowPos() the window no longer moves (of course) but the event stream is now consistent.

This leads me to think that the moving of the window, when done quickly is causing the jerking motion (ie. 2 steps forward, 1 step backwards).

2

There are 2 answers

1
K. A. Buhr On BEST ANSWER

I suspect your problem is that the cursor_pos_callback receives the cursor position relative to the window and moving the window has an immediate effect on that position.

Suppose you are moving the cursor diagonally at a constant rate. If, over one tick, the cursor moves from relative position (100,100) to (105,105), you calculate delta_x=5 and delta_y=5. You then move the window. The process of moving the window then instantaneously changes the relative coordinates from (105,105) back to (100,100), and on the next tick, even though you've moved to position (110,110) relative to the original window location, you are only at relative position (105,105) with respect to the new window location, so you calculate delta_x=0 and delta_y=0 from your previous position (plus some random jittery noise) even though you've actually moved an addition 5 units along each axis.

Instead, modify your algorithm to maintain a constant relative cursor position. On the drag start, store the relative cursor position (say (100,100)). Now, at each tick, calculate where you need to position the window to move the cursor back to that fixed, relative position. So, if the cursor has moved to (112,108), move the window by (+12,+8) to put the cursor back at (100,100). At a later tick, if the cursor has moved to (108,106), don't try to calculate a delta from (112,108); instead, compare it to the original (100,100) starting point and move the window by (+8,+6). It would be something like the following:

cursor_pos_callback(GLFWwindow *win, double xpos, double ypos) {

  // update window position
  if (window_drag_active) {
     int delta_x = xpos - window_drag_starting_xpos;
     int delta_y = ypos - window_drag_starting_ypos;
     int x,y;
     glfwGetWindowPos(window, &x, &y);
     glfwSetWindowPos(window, x + delta_x, y + delta_y);
  }
}
0
Keyboard Princess On

I marked the other answer as the accepted solution, as it's true that you need to record the initial starting cursor position (relative to the window) and use that to calculate the deltas.

However doing that only provided half of the solution. I still experienced some major jumping and shooting across the screen when dragging!

In the end I found out that this sync issue was from where I was setting the new window position.

To get rid of any lag completely, and ensure window + cursor are synced up, you must do the glfwGetWindowPos() and glfwSetWindowPos() at the same time inside of the render loop!

Doing so inside of the callback will result in jitter with positions becoming out of sync. So I believe part of the solution is also making sure to keep window + cursor in sync as much as possible.

Here is a minimal example, that I came to (many thanks to K.A. Buhr!)

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#include <GLFW/glfw3.h>

static double cursor_pos_x = 0;
static double cursor_pos_y = 0;
static double delta_x = 0;
static double delta_y = 0;

static int window_drag_active = 0;

static void mouse_button_callback(GLFWwindow *window, int button, int action,
                              int mods) {
    if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) {
        window_drag_active = 1;
        double x, y;
        glfwGetCursorPos(window, &x, &y);
        cursor_pos_x = floor(x);
        cursor_pos_y = floor(y);
    }
    if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_RELEASE) {
        window_drag_active = 0;
    }
}

int main() {
    glfwInit();

    GLFWwindow *win = glfwCreateWindow(400, 500, "Drag Example", NULL, NULL);

    glfwSetMouseButtonCallback(win, mouse_button_callback);
    glfwMakeContextCurrent(win);

    while (!glfwWindowShouldClose(win)) {
    glfwWaitEvents();

    if (window_drag_active) {
        double xpos, ypos;
        glfwGetCursorPos(win, &xpos, &ypos);
        delta_x = xpos - cursor_pos_x;
        delta_y = ypos - cursor_pos_y;

        int x, y;
        glfwGetWindowPos(win, &x, &y);
        glfwSetWindowPos(win, x + delta_x, y + delta_y);
    }

    glfwSwapBuffers(win);
    }
    return 0;
}