Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rcore] [GLFW] Mouse relative, mouse position, mouse delta issues #4665

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

asdqwe
Copy link
Contributor

@asdqwe asdqwe commented Jan 7, 2025

Do NOT merge this

This PR is just meant to demonstrate the scope of the issue.

  1. After starting GLFW and for about 3-5 frame into the main loop, the mouse coords will not be using coords relative to the top left edge of the window (aka -400 to 1200 for me here). It will, instead, be using the standard coords (aka 0 to 1600 for me here). After those initial frames, it will then swap into using mouse coords relative to the top left edge of the window (aka -400 to 1200 for me here) for both regular cursor (GLFW_CURSOR_NORMAL) or relative cursor (GLFW_CURSOR_DISABLED).
Logs for item 1:

Note: in all cases (GLFW_CURSOR_DISABLED, GLFW_CURSOR_NORMAL, outside, inside, bottom right, top left) the first frames use a different coord plot. On GLFW_CURSOR_DISABLED inside top left (ish) (the last one) it appears correct, but just because the positions will "match" giving the illusion the coords are the same.

GLFW_CURSOR_NORMAL outside bottom right:
0.054654 S1 mouse pos 1598.000000 845.000000
0.054850 S2 mouse pos 1598.000000 845.000000
0.057189 S3 mouse pos 1598.000000 845.000000
0.060459 ML mouse pos 1598.000000 845.000000
0.061422 ML mouse pos 1598.000000 845.000000
0.061612 ML mouse pos 1598.000000 845.000000
0.076255 ML mouse pos 1199.000000 674.000000
0.092925 ML mouse pos 1199.000000 674.000000
0.109862 ML mouse pos 1199.000000 674.000000
GLFW_CURSOR_NORMAL outside top left:
0.053905 S1 mouse pos -1.000000 -54.000000
0.058312 S2 mouse pos -1.000000 -54.000000
0.064230 S3 mouse pos -1.000000 -54.000000
0.064299 ML mouse pos -1.000000 -54.000000
0.066624 ML mouse pos -1.000000 -54.000000
0.068199 ML mouse pos -1.000000 -54.000000
0.068823 ML mouse pos -1.000000 -54.000000
0.086571 ML mouse pos -400.000000 -225.000000
0.102811 ML mouse pos -400.000000 -225.000000
0.119698 ML mouse pos -400.000000 -225.000000
GLFW_CURSOR_NORMAL inside bottom right (ish) of the window area:
0.059779 S1 mouse pos 1141.000000 566.000000
0.061506 S2 mouse pos 1141.000000 566.000000
0.063629 S3 mouse pos 1141.000000 566.000000
0.063878 ML mouse pos 1141.000000 566.000000
0.066120 ML mouse pos 1141.000000 566.000000
0.067445 ML mouse pos 1141.000000 566.000000
0.074396 ML mouse pos 1141.000000 566.000000
0.097091 ML mouse pos 742.000000 395.000000
0.123962 ML mouse pos 742.000000 395.000000
0.125808 ML mouse pos 742.000000 395.000000
0.141357 ML mouse pos 742.000000 395.000000
GLFW_CURSOR_NORMAL inside top left (ish) of the window area:
0.059214 S1 mouse pos 505.000000 219.000000
0.059659 S2 mouse pos 505.000000 219.000000
0.066630 S3 mouse pos 505.000000 219.000000
0.066963 ML mouse pos 505.000000 219.000000
0.072521 ML mouse pos 505.000000 219.000000
0.073020 ML mouse pos 505.000000 219.000000
0.084910 ML mouse pos 106.000000 48.000000
0.101700 ML mouse pos 106.000000 48.000000
0.118353 ML mouse pos 106.000000 48.000000
GLFW_CURSOR_DISABLED outside bottom right:
0.068877 S1 mouse pos 1598.000000 845.000000
0.068988 S2 mouse pos 1598.000000 845.000000
0.069131 S3 mouse pos 1598.000000 845.000000
0.069143 ML mouse pos 1598.000000 845.000000
0.085844 ML mouse pos 400.000000 225.000000
0.087235 ML mouse pos 400.000000 225.000000
0.091461 ML mouse pos 400.000000 225.000000
GLFW_CURSOR_DISABLED outside top left:
0.059876 S1 mouse pos -1.000000 -54.000000
0.063243 S2 mouse pos -1.000000 -54.000000
0.073733 S3 mouse pos -1.000000 -54.000000
0.073744 ML mouse pos -1.000000 -54.000000
0.092462 ML mouse pos 400.000000 225.000000
0.093808 ML mouse pos 400.000000 225.000000
0.097515 ML mouse pos 400.000000 225.000000
GLFW_CURSOR_DISABLED inside bottom right (ish) of the window area:
0.058279 S1 mouse pos 1127.000000 556.000000
0.058708 S2 mouse pos 1127.000000 556.000000
0.075634 S3 mouse pos 1127.000000 556.000000
0.075653 ML mouse pos 1127.000000 556.000000
0.089373 ML mouse pos 400.000000 225.000000
0.089777 ML mouse pos 400.000000 225.000000
0.092417 ML mouse pos 400.000000 225.000000
GLFW_CURSOR_DISABLED inside top left (ish) of the window area:
0.063160 S1 mouse pos 470.000000 214.000000
0.066787 S2 mouse pos 470.000000 214.000000
0.066888 S3 mouse pos 470.000000 214.000000
0.066895 ML mouse pos 470.000000 214.000000
0.072997 ML mouse pos 470.000000 214.000000
0.073430 ML mouse pos 470.000000 214.000000
0.089546 ML mouse pos 470.000000 214.000000
0.105993 ML mouse pos 470.000000 214.000000
  1. In case relative cursor (GLFW_CURSOR_DISABLED) is enabled through a glfwSetInputMode() call, an event (FocusIn on Linux, WM_CAPTURECHANGED | WM_SETFOCUS on Windows, NSNotification on MacOS) inside the internal polling (_glfwPollEventsX11 on Linux, _glfwPollEventsWin32 on Windows, not sure on MacOS) will trigger a disable cursor sequence (disableCursor on Linux, disableCursor on Windows, directly on MacOS), which will save the cursor, center it and call for it to be captured.

  2. This becomes a problem when trying to start the program with the cursor disabled (aka cursor relative, GLFW_CURSOR_DISABLED) because during those first frames the cursor will have a given position and when the cursor disabled kicks in, it will warp into a diferent position, causing cursor delta calculations to spike, making things like free cameras jolt suddenly.

  3. Trying to set the cursor position before the program main loop (e.g.: inside raylib InitPlatform) proved to be ineffective, because it may not run soon enough (sometimes missing 1 or 2 frames). And mostly because when the events (mentioned on item 2) kick in, they will force the cursor position to be "re-centered" breaking the previous position, causing the delta calculation to spike and jolting anything that depends on it.

Logs for itens 2 and 4:

With disableCursor forced call and _glfwCenterCursorInContentArea unchanged:

0.064080 S1 mouse pos -1.000000 -54.000000
0.064243 S2 mouse pos -1.000000 -54.000000
>>> disableCursor forced call <<<
>>> glfwSetInputMode called <<<
0.069536 I1 mouse VPO 400.000000 225.000000
0.069923    mouse pos 400.000000 225.000000
0.069930 I2 mouse VPO 400.000000 225.000000
0.071111    mouse pos 400.000000 225.000000
0.071132 S3 mouse pos 400.000000 225.000000
0.071137 ML mouse pos 400.000000 225.000000
0.076082 ML mouse pos 400.000000 225.000000
>>> FocusIn event <<<
0.080571 ML mouse pos 400.000000 225.000000
0.089903 ML mouse pos 400.000000 225.000000
0.104371 ML mouse pos 400.000000 225.000000
0.120844 ML mouse pos 400.000000 225.000000
0.137927 ML mouse pos 400.000000 225.000000
0.154749 ML mouse pos 400.000000 225.000000
0.171256 ML mouse pos 400.000000 225.000000
0.188236 ML mouse pos 400.000000 225.000000
0.204698 ML mouse pos 400.000000 225.000000
0.221681 ML mouse pos 400.000000 225.000000
0.238290 ML mouse pos 400.000000 225.000000
0.255109 ML mouse pos 400.000000 225.000000
0.271776 ML mouse pos 400.000000 225.000000
0.288486 ML mouse pos 400.000000 225.000000
0.305169 ML mouse pos 400.000000 225.000000
0.321914 ML mouse pos 400.000000 225.000000
>>> mouse move <<<
0.338653 ML mouse pos 1.000000 53.000000
0.355327 ML mouse pos -66.000000 18.000000
0.371961 ML mouse pos -254.000000 -34.000000
0.388471 ML mouse pos -470.000000 -62.000000
0.405476 ML mouse pos -640.000000 -72.000000
0.422202 ML mouse pos -726.000000 -72.000000
0.438943 ML mouse pos -747.000000 -76.000000
0.455550 ML mouse pos -747.000000 -76.000000

With disableCursor forced call and _glfwCenterCursorInContentArea commented:

0.062170 S1 mouse pos -1.000000 -54.000000
0.068435 S2 mouse pos -1.000000 -54.000000
>>> disableCursor forced call <<<
### _glfwCenterCursorInContentArea commented ###
>>> glfwSetInputMode called <<<
### _glfwCenterCursorInContentArea commented ###
0.075644 I1 mouse VPO 0.000000 0.000000
0.086601    mouse pos 0.000000 0.000000
0.086620 I2 mouse VPO 0.000000 0.000000
0.088074    mouse pos 0.000000 0.000000
0.088096 S3 mouse pos 0.000000 0.000000
0.088104 ML mouse pos 0.000000 0.000000
0.094048 ML mouse pos 0.000000 0.000000
0.095345 ML mouse pos 0.000000 0.000000
>>> FocusIn event <<<
0.099495 ML mouse pos 0.000000 0.000000
0.103130 ML mouse pos 0.000000 0.000000
0.121445 ML mouse pos 0.000000 0.000000
0.136934 ML mouse pos 0.000000 0.000000
0.153888 ML mouse pos 0.000000 0.000000
0.170600 ML mouse pos 0.000000 0.000000
0.187285 ML mouse pos 0.000000 0.000000
0.204082 ML mouse pos 0.000000 0.000000
0.220870 ML mouse pos 0.000000 0.000000
0.237450 ML mouse pos 0.000000 0.000000
0.254247 ML mouse pos 0.000000 0.000000
0.270865 ML mouse pos 0.000000 0.000000
0.287571 ML mouse pos 0.000000 0.000000
0.304250 ML mouse pos 0.000000 0.000000
0.321057 ML mouse pos 0.000000 0.000000
>>> mouse move <<<
0.337745 ML mouse pos -396.000000 -171.000000
0.354416 ML mouse pos -395.000000 -170.000000
0.371331 ML mouse pos -392.000000 -168.000000
0.388017 ML mouse pos -392.000000 -168.000000
0.404616 ML mouse pos -392.000000 -168.000000
  1. A workaround for this required two steps: first it was necessary to make an early call to disableCursor(). To avoid meddling with GLFW, one can create a similar funcition to disableCursor() and call that instead (it would look like this on Linux, like this on Windows, probably similar to this on MacOS). This raylibFixEarlyDisableCursor() then has to be called before DisableCursor(), preferably right after InitPlatform() and would "fix" those first frames having the wrong mouse coords.

  2. Then the second part is making GLFW's internal event polling (_glfwPollEventsX11 on Linux, _glfwPollEventsWin32 on Windows, not sure on MacOS) always "re-center" because, without it, when the mouse is moved, it's moved position will not be the centered one it's supposed to be and will cause the cursor delta calculations to spike again, making things like free cameras jolt suddenly again. Also, by the comments, this apparently have an implication for glfwWaitEvents(), although I couldn't notice any problems from it while using glfwWaitEvents() on the tests.

  3. Yet, when I think this solves the problem I discover that the top left quadrant of the window is now broken (at least on Linux) for GLFW. For some reason, it will break the mouse coords on that quadrant alone to a point of requiring special handling just for it. Not only that, it also makes CORE.Window.screen sizes return wrong values. Which, makes no sense, because the lines commented only update the cursor and are there, apparently, just for performance/stability on glfwWaitEvents(), besides, and all other changes are copies of disableCursor() variants. I even double checked it with a fresh repository.

It was necessary to add this to the start of raylib DisableCursor() just to handle that quadrant:
double xpos, ypos;
glfwGetCursorPos(platform.handle, &xpos, &ypos);
// This if was modified from @ve-nt commit: https://github.com/ve-nt/raylib/commit/28975df893e223fdd69051a9289358d4918f6720
if ((xpos < 0 || xpos > CORE.Window.screen.width) || (ypos < 0 || ypos > CORE.Window.screen.height))
{   // Outside
    SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2);
}
else
{   // Inside
    int w, h;
    glfwGetWindowSize(platform.handle, &w, &h);
    if (((float)xpos < (w/2)) && ((float)ypos < (h/2)))
    {
        // Top left quadrant bug
        CORE.Input.Mouse.currentPosition = (Vector2){ (float)xpos+(w/2), (float)ypos+(h/2) };
        CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
        glfwSetCursorPos(platform.handle, CORE.Input.Mouse.currentPosition.x, CORE.Input.Mouse.currentPosition.y);
    }
    else
    {
        CORE.Input.Mouse.currentPosition = (Vector2){ (float)xpos, (float)ypos };
        CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
        glfwSetCursorPos(platform.handle, CORE.Input.Mouse.currentPosition.x, CORE.Input.Mouse.currentPosition.y);
    }
}

For this window quadrant:

img

  1. At this point it was clear that this wasn't a viable solution and the more I kept digging, the worst it got. Maybe I just don't get how GLFW works well enough to fix this. Or maybe Linux (and/or MacOS) are broken on it on this part. Perhaps it's something really obvious that I couldn't spot, who knows.

  2. Either way, for anyone that would like to continue debugging this issue it's confirmed to affect Linux and MacOS. The issue report is posted at #4654. A good way to debug this is directly with GLFW standalone, so raylib code won't interfere, at least on the initial testing. I've left some standalone sample tests below (including compiling instructions):

Sample tests:
Basic GLFW standalone test:
//     How to replicate:
// unzip glfw-master.zip
// cd glfw-master/
//     Pick any example from examples/ and replace its code with this source.
//     Then:
// mkdir build/
// cmake -S . -B build/
// cmake --build build/
// ./build/examples/example

#include <stdio.h>
#define GLAD_GL_IMPLEMENTATION
#include <glad/gl.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>

int main(int argc, char** argv) {

    // Init GLFW:
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);

    // Create a window:
    GLFWwindow *window;
    window = glfwCreateWindow(800, 450, "test", NULL, NULL);
    if (!window) { glfwTerminate(); exit(EXIT_FAILURE); }

    // Center the window on first monitor:
    GLFWmonitor *monitor = glfwGetPrimaryMonitor();
    const GLFWvidmode *mode = glfwGetVideoMode(monitor);
    glfwSetWindowPos(window, mode->width/4, mode->height/4);

    // Prepare render:
    glfwMakeContextCurrent(window);
    gladLoadGL(glfwGetProcAddress);
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glfwSwapInterval(1);

    // Enable GLFW_CURSOR_DISABLED:
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // Main loop:
    while (!glfwWindowShouldClose(window)) {

        // Print mouse pos:
        double x, y;
        glfwGetCursorPos(window, &x, &y);
        printf("mouse pos %f %f\n", x, y);

        // Render buffer:
        glClear(GL_COLOR_BUFFER_BIT);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // Quit:
    glfwDestroyWindow(window);
    glfwTerminate();
    exit(EXIT_SUCCESS);

}
Specific GLFW standalone test:
//     How to replicate:
// unzip glfw-master.zip
// cd glfw-master/
//     Pick any example from examples/ and replace its code with this source.
//     Then:
// mkdir build/
// cmake -S . -B build/
// cmake --build build/
// ./build/examples/example

#include <stdio.h>
#define GLAD_GL_IMPLEMENTATION
#include <glad/gl.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>

int main(int argc, char** argv) {

    // Init GLFW:
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);

    // Create a window:
    GLFWwindow *window;
    window = glfwCreateWindow(800, 450, "test", NULL, NULL);
    if (!window) { glfwTerminate(); exit(EXIT_FAILURE); }

    // Center the window on first monitor:
    GLFWmonitor *monitor = glfwGetPrimaryMonitor();
    const GLFWvidmode *mode = glfwGetVideoMode(monitor);
    glfwSetWindowPos(window, mode->width/4, mode->height/4);

    // Prepare render:
    glfwMakeContextCurrent(window);
    gladLoadGL(glfwGetProcAddress);
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glfwSwapInterval(1);

    // Reusable vars:
    double x, y;

    glfwGetCursorPos(window, &x, &y);
    printf("%f S1 mouse pos %f %f\n", glfwGetTime(), x, y);

    raylibFixEarlyDisableCursor(window);
    printf(">>> raylibFixEarlyDisableCursor called <<<\n");

    glfwGetCursorPos(window, &x, &y);
    printf("%f S2 mouse pos %f %f\n", glfwGetTime(), x, y);

    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    printf(">>> glfwSetInputMode called for GLFW_CURSOR_DISABLED <<<\n");

    glfwGetCursorPos(window, &x, &y);
    printf("%f S3 mouse pos %f %f\n", glfwGetTime(), x, y);

    // Main loop:
    while (!glfwWindowShouldClose(window)) {

        glfwGetCursorPos(window, &x, &y);
        printf("%f ML mouse pos %f %f\n", glfwGetTime(), x, y);

        // Render buffer:
        glClear(GL_COLOR_BUFFER_BIT);
        glfwSwapBuffers(window);

        //glfwPollEvents();
        glfwWaitEvents();

    }

    // Quit:
    glfwDestroyWindow(window);
    glfwTerminate();
    exit(EXIT_SUCCESS);

}
Raylib test:
//     How to replicate:
// unzip raylib-master.zip
// cd raylib-master/src/
// make PLATFORM=PLATFORM_DESKTOP_GLFW
// cd ../examples/
//     Pick the core/core_basic_window.c example and replace its code with this source.
//     Then:
// make PLATFORM=PLATFORM_DESKTOP_GLFW core/core_basic_window
// ./core/core_basic_window

#include "raylib.h"

int main(void) {
    SetTraceLogLevel(LOG_WARNING);
    InitWindow(800, 450, "test");
    SetTargetFPS(60);

    DisableCursor();

    while (!WindowShouldClose()) {
        TraceLog(LOG_WARNING, "time:%.2f   pos: %.2f x %.2f   delta: %.2f x %.2f", GetTime(), GetMousePosition().x, GetMousePosition().y, GetMouseDelta().x, GetMouseDelta().y);
        BeginDrawing();
        ClearBackground(RAYWHITE);
        EndDrawing();
    }
    CloseWindow();
    return 0;
}
  1. This PR intentionally doesn't work (didn't expose raylibFixEarlyDisableCursor() on purpose) because I think these changes are a bad idea. I'm making it just to document everything I could find in a compact easy to check point for anyone that wants to continue to debug.
    @raysan5 please close this PR after you finish reading it.
    @ve-nt @alexnivanov, I'm sorry guys, I really tried.

@asdqwe asdqwe closed this Jan 7, 2025
@asdqwe asdqwe deleted the fix-mouse branch January 7, 2025 20:11
@asdqwe asdqwe restored the fix-mouse branch January 7, 2025 20:22
@asdqwe asdqwe reopened this Jan 8, 2025
@asdqwe asdqwe changed the title [rcore] [GLFW] Init mouse positions [rcore] [GLFW] Mouse relative, mouse position, mouse delta issues Jan 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant