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] Initial position reported by GetMouseDelta() incorrect #4654

Open
4 tasks done
alexnivanov opened this issue Jan 3, 2025 · 37 comments · Fixed by #4657
Open
4 tasks done

[rcore] Initial position reported by GetMouseDelta() incorrect #4654

alexnivanov opened this issue Jan 3, 2025 · 37 comments · Fixed by #4657
Labels
external This issue depends on external lib

Comments

@alexnivanov
Copy link

  • I tested it on latest raylib version from master branch
  • I checked there is no similar issue already reported
  • I checked the documentation on the wiki
  • My code has no errors or misuse of raylib

Issue description

I'm having an issue with camera update on MacOS: when the app starts, and I first move the mouse, GetMouseDelta() is very far away. After doing some experiments I figured out that it's assuming that the mouse is initially in the center of the screen. When I start the app having previously moved the mouse to the center of the screen, the behaviour is OK.

I've build from source core_3d_camera_free, and the issue is reproduced as described above.

Environment

MacBook M1 Pro, MacOS Sequoia 15.1.1, OpenGL Version: 4.1 Metal - 89.3.

Issue Screenshot

Just touched my mouse and got this:
image

Code Example

examples/core_3d_camera_free reproduces the issue.

@CodingMadness
Copy link

did you close the issue already, lol?

@alexnivanov
Copy link
Author

@asdqwe have you tested the fix? I've tried, and it didn't resolve anything. Also, the issue seems to be unrelated to DisableCursor method: mouse delta should be appropriate when cursor is enabled as well!

@raysan5 raysan5 reopened this Jan 6, 2025
@alexnivanov
Copy link
Author

Comment line rcore_desktop_glfw.c#L1021 to see the diff.

Yeah well I did exactly that, and there seems to be no difference on MacOS :(. Also as mentioned above, it seems that the fix should be unrelated to DisableCursor method.

@alexnivanov
Copy link
Author

@asdqwe I've checked GLFW, it is a well known behaviour, see this example: https://learnopengl.com/Getting-started/Camera

The solution is to ignore first mouse update:

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }
   ...
}

However, in rcore_desktop_glfw.c, there is no such logic in MouseCursorPosCallback, hence the result. I'm not sure where to put this temporary firstMouse variable, should be reviewed by somebody familiar with the library architecture.

@alexnivanov
Copy link
Author

When cursor is enabled, the mouse delta shouldn't matter

I don’t understand why do you claim that. When you have a cursor and the window appears, mouse delta should be continuous as well.

@ve-nt
Copy link

ve-nt commented Jan 7, 2025

I can confirm I see the same issue on Linux using the GLFW backend.

@asdqwe I've checked GLFW, it is a well known behaviour, see this example: https://learnopengl.com/Getting-started/Camera

The solution is to ignore first mouse update:

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }
   ...
}

This appears to be correct. I have implemented this solution in my PR (#4663) and can confirm it fixes the camera jump on my machine.

@alexnivanov
Copy link
Author

alexnivanov commented Jan 7, 2025

I have implemented this solution in my PR (#4663) and can confirm it fixes the camera jump on my machine.

I can confirm that this PR fixes the issue for me as well as with enabled and disabled cursor!

@raysan5
Copy link
Owner

raysan5 commented Jan 7, 2025

@asdqwe thanks for all the time put on reviewing this issue, definitely the target platform does not deserve it.

@alexnivanov @ve-nt Am I understanding correctly that the solution to the issue is a macOS-platforn-specific-hack for first mouse input read, requiring a global variable to manage it? No way.

@ve-nt
Copy link

ve-nt commented Jan 7, 2025

I don't believe the issue is limited to macOS, as I'm seeing the same issue on Linux. I think the issue is in the GLFW backend in general. I have experienced this same issue when working with GLFW+OpenGL directly, in fact this issue is brought up and fixed as part of the LearnOpenGL book, using the same solution I've implemented in my PR.

See Mouse Input section at: https://learnopengl.com/Getting-started/Camera

Depends on your definition of "global variable" I guess. In my PR its a static local variable inside the callback.

@raysan5
Copy link
Owner

raysan5 commented Jan 7, 2025

Depends on your definition of "global variable" I guess. In my PR its a static local variable inside the callback.

Afaik, the lifetime is since first run to the end of the program, so effectively it's a global variable that will require some bytes forever (while the program running).

Still, I think this issue should be addressed at GLFW side, not hacked at raylib side. Thanks for reporting it's not macOS specific, I'm changing the title.

@raysan5 raysan5 changed the title [rcore] Initial GetMouseDelta wrong for MacOS [rcore] Initial position reported by GetMouseDelta() incorrect Jan 7, 2025
@raysan5
Copy link
Owner

raysan5 commented Jan 7, 2025

As per the details of the issue and the further clarification, it seems this issue is related to GLFW, so I'm afraid it should be addressed by GLFW, not hacked at raylib side.

@raysan5 raysan5 closed this as completed Jan 7, 2025
@raysan5 raysan5 added the external This issue depends on external lib label Jan 7, 2025
@ve-nt
Copy link

ve-nt commented Jan 7, 2025

I'm not sure that the problem is in the GLFW side. As far as I can tell, its because the CORE.Input.Mouse.previousPosition is zero on the first mouse movement, since MouseCursorPosCallback is only called when the mouse is moved. This means that the first GetMouseDelta after the mouse is moved for the first time will have an accurate currentPosition but a zeroed previousPosition, leading to a huge delta and therefore a camera jolt.

@raysan5
Copy link
Owner

raysan5 commented Jan 7, 2025

@ve-nt I see, can it be managed at user side in that case? Actually GetMouseDelta() was added quite recently, before it was up to the user to track mouse delta...

@ve-nt
Copy link

ve-nt commented Jan 7, 2025

I have been trying to fix this on the user side but not had much luck so far. I'm open to any ideas and will keep you posted if I find a fix on the user side.

@raysan5 raysan5 reopened this Jan 7, 2025
@raysan5
Copy link
Owner

raysan5 commented Jan 7, 2025

I'm reopening for a closer look, in case of a better solution than proposed hack...

@alexnivanov
Copy link
Author

@raysan5 I see the root cause in SetMousePosition being called with coordinates of the center of the screen assuming that the cursor will be there by both EnableCursor and DisableCursor which is obviously not true: it doesn't affect actual cursor position hence we get this initial invalid delta.

@alexnivanov
Copy link
Author

@alexnivanov @ve-nt @raysan5 Please test #4665. Hopefully it fixes this so we can be done with it.

@asdqwe this is better, but if you call either DisableCursor() or EnableCursor() it breaks anyway :(

@ve-nt
Copy link

ve-nt commented Jan 7, 2025

@alexnivanov @ve-nt @raysan5 Please test #4665. Hopefully it fixes this so we can be done with it.

I'm afraid I've tested it with examples/core/core_3d_camera_free and the camera still jolts on the first mouse movement.

It only jolts when the cursor is positioned inside the window from the start. There's no jolt if the cursor was outside the window when the window opened. Is this the same for you @alexnivanov?

@alexnivanov
Copy link
Author

@ve-nt it does jolt for me not depending on initial cursor position being inside/outside of the window. Only removing DisableCursor() call removes the jolt, so it's a good starting point anyway :)

@ve-nt
Copy link

ve-nt commented Jan 7, 2025

So after a bit more digging, this bug is weirder than I thought at first.

It appears that SetMousePosition is not functioning properly...

When my cursor is outside of the created window, it seems to work fine. But when the cursor is over the created window, GLFW does not actually move the cursor.

This means that when MouseCursorPosCallback is called for the first time, GLFW still has the cursor in its original position (i.e. where it was before the window was opened). Meaning the current/previousPosition is out of sync with where the cursor actually is.

This is what is leading to the spike in the mouse delta, and hence why I don't get a camera jolt unless my cursor is over the window when its opened.

Its worth me reiterating that GLFW seems to be setting the cursor position fine if my cursor is not over the window. The coordinates reported from the first callback match up with where DisableCursor is setting the cursor to.

Perhaps this is a GLFW issue after all?

@raysan5
Copy link
Owner

raysan5 commented Jan 7, 2025

@asdqwe thanks for the further review. Please, take it easy, no hurries at all.

@ve-nt
Copy link

ve-nt commented Jan 7, 2025

Please see my fix here: ve-nt@28975df

This fixes the issue on my machine, although I don't quite understand why yet, hence why I haven't submitted it as a PR. Though it may help if others want to look.

The thing I don't understand is why GLFW isn't setting its cursor position properly. I believe this is key to why our mouse deltas are wrong. Our SetMousePosition function assumes that GLFW will take our coordinates and use them, and when it doesn't we get out of sync with it.

@ve-nt
Copy link

ve-nt commented Jan 8, 2025

For my 1600x900 screen it's plotting a -400 to 1200 horizontal mouse coord space

This makes sense to me as glfwGetCursorPos returns the coordinates relative to the upper-left corner of the window. So if your window is placed at x400, then the very left side of your screen will be -400, and the very right will be 1200.

@ve-nt
Copy link

ve-nt commented Jan 8, 2025

@ve-nt Ok, ve-nt@28975df didn't work for DisableCursor() + Outside on the LEFT side here:

WARNING: time:0.08   pos: 158.00 x 388.00   delta: 0.00 x 0.00
WARNING: time:0.10   pos: 400.00 x 225.00   delta: 242.00 x -163.00
WARNING: time:0.10   pos: 400.00 x 225.00   delta: 0.00 x 0.00
WARNING: time:0.12   pos: 400.00 x 225.00   delta: 0.00 x 0.00
WARNING: time:0.13   pos: 400.00 x 225.00   delta: 0.00 x 0.00

Strange... It works on the outside left for me.

Could you also print out the x and y arguments that MouseCursorPosCallback is receiving? That's what I've been trying to check. Just so we can compare.

Also, from the GLFW header:

 *  __Do not use this function__ to implement things like camera controls.  GLFW
 *  already provides the `GLFW_CURSOR_DISABLED` cursor mode that hides the
 *  cursor, transparently re-centers it and provides unconstrained cursor
 *  motion.  See @ref glfwSetInputMode for more information.

transparently re-centers it

I get the feeling this might have something to do with it but I'm not sure yet.

@ve-nt
Copy link

ve-nt commented Jan 8, 2025

I'm seeing the same weird results with MouseCursorPosCallback. Although the pos your printing seems off, it should be negative if its to the outside and to the left of your window. Perhaps check it against the straight output of glfwGetCursorPos.

I'm tracing the reported values from glfwGetCursorPos during InitPlatform. I've also added a reread of them at the end of DisableCursor.

Here's what I'm seeing with my cursor OUTSIDE and to the LEFT:

WARNING: InitPlatform: glfwGetCursorPos: -156.000000 282.000000
WARNING: DisableCursor: glfwGetCursorPos: -156.000000 282.000000
WARNING: MouseButtonCallback: 409.000000 225.000000
WARNING: MouseButtonCallback: 408.000000 226.000000

The inputs to MouseButtonCallback seem completely different to glfwGetCursorPos!

Meanwhile, here's the output with my cursor INSIDE:

WARNING: InitPlatform: glfwGetCursorPos: 180.000000 214.000000
WARNING: DisableCursor: glfwGetCursorPos: 180.000000 214.000000
WARNING: MouseButtonCallback: 180.000000 215.000000
WARNING: MouseButtonCallback: 180.000000 216.000000
WARNING: MouseButtonCallback: 180.000000 217.000000

They match up great! Hence no jolt when my cursor is inside, but huge jolt when outside...

I should mention that this is the same whether I SetMousePosition before DISABLE_CURSOR or not. Seems to be the same either way. Perhaps we enter a different coord space once in DISABLE_CURSOR? Although doesn't feel like it. Will have to check the docs tomorrow as its getting late here.

@ve-nt
Copy link

ve-nt commented Jan 8, 2025

Another hint from what I've just noticed:

My MouseButtonCallback x and y always start at 409 and 225 if my cursor started outside the window (no matter left, right, up, or down).

@asdqwe
Copy link
Contributor

asdqwe commented Jan 8, 2025

Here's a standalone GLFW 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);

}
Here's my current test (with the prints):
//     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 <unistd.h>

#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);

//glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);

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

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

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();
    }

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

}

Conclusions so far:

  1. GLFW_CURSOR_DISABLED is "re-centering" itself, but not how we usually expect it.
    The doc first mentions "[...] GLFW_CURSOR_DISABLED cursor mode that hides the cursor, transparently re-centers it [...]", but, in the next paragraph, says "If the cursor mode is GLFW_CURSOR_DISABLED then the cursor position is unconstrained and limited only by the minimum and maximum values of a double.", which is what's happening in practice.
    So, it's getting centered on the start, but once it's there, the coords are not "re-centered".

  2. After starting GLFW and for about 3-5 frame into the main loop, the mouse coors will not be using mouse coordinates relative to the left edge of the window (aka -400 to 1200 to me here). It will be using instead the standard coords (aka 0 to 1600 to me here).
    After those initial frames, it will then, at an undetermined moment, swap into using mouse coordinates relative to the left edge of the window (aka -400 to 1200 to me here) (see logs).

  3. When calling glfwSetInputMode() for GLFW_CURSOR_DISABLED it will eventually properly automatically center the mouse coords to 400x255 (aka center of the window to me here), but only after a good 5-7 total frames using the standard coords (see logs).

  4. The same 5-7 frames using a different coord system will also happen if we're using GLFW_CURSOR_NORMAL. It just won't center the cursor (see logs).

Logs:

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. These first frames, regardless of mouse mode, using a different coord system is what's messing with raylib operation. When we try to "fix" the mouse position on the start, it will end up falling into the wrong "phase" with the different coords.
    Now have to find either:

    1. Where the coords are changing on GLFW;
    2. Or, at which point it's safe to set the mouse position;
    3. Or, if we can "fake"/"overload" the coords so we "force" them to match.

@ve-nt
Copy link

ve-nt commented Jan 8, 2025

From what I'm seeing, it appears that https://github.com/raysan5/raylib/blob/fc29bc27fd1c6904143fc24fbcf5ca629ec491c6/src/external/glfw/src/input.c#L545C1-L553C2 only works when my cursor is outside of the window.

When its inside, the cursor indeed reports that its centered, but the x and y in the mousepos callback still seem to have the old uncentered value.

For context, this is me disabling all the cursor relocations that raylib is doing, and reporting the values from GLFW directly. I'll have a go with your isolated GLFW example when I've got some time later today.

When INSIDE:

>>>>>>>>>>>>>>>> _glfwCenterCursorInContentArea: WIN SIZE 818 450
>>>>>>>>>>>>>>>> _glfwCenterCursorInContentArea: GLFW NEW CURSOR POS 409.000000 225.000000
INFO: TIMER: Target time per frame: 16.667 milliseconds
>>>>>>>>>>>>>>>> _glfwCenterCursorInContentArea: WIN SIZE 818 450
>>>>>>>>>>>>>>>> _glfwCenterCursorInContentArea: GLFW NEW CURSOR POS 409.000000 225.000000
WARNING: MouseButtonCallback OLD: 400.000000 225.000000   400.000000 225.000000
WARNING: MouseButtonCallback: 186.000000 249.000000   <--- BAD!

When OUTSIDE:

>>>>>>>>>>>>>>>> GLFW_CURSOR_DISABLED
>>>>>>>>>>>>>>>> _glfwCenterCursorInContentArea: WIN SIZE 818 450
>>>>>>>>>>>>>>>> _glfwCenterCursorInContentArea: GLFW NEW CURSOR POS 409.000000 225.000000
INFO: TIMER: Target time per frame: 16.667 milliseconds
WARNING: MouseButtonCallback OLD: 400.000000 225.000000   400.000000 225.000000
WARNING: MouseButtonCallback: 409.000000 225.000000
>>>>>>>>>>>>>>>> _glfwCenterCursorInContentArea: WIN SIZE 818 450
>>>>>>>>>>>>>>>> _glfwCenterCursorInContentArea: GLFW NEW CURSOR POS 409.000000 225.000000
WARNING: MouseButtonCallback OLD: 409.000000 225.000000   409.000000 225.000000
WARNING: MouseButtonCallback: 408.000000 225.000000
WARNING: MouseButtonCallback OLD: 408.000000 225.000000   408.000000 225.000000
WARNING: MouseButtonCallback: 408.000000 226.000000  <--- GOOD!

I'm also going to do a debugging session with GLFW later on to try and track down why this cursor pos isn't setting properly.

Considering Raylib has the option of building its own GLFW, I think that correcting this issue on our end and pushing it upstream would be the best approach if we can manage it. Even if it takes GLFW a long time to take the patch upstream, Raylib will at least benefit from the fix in the meantime (when building GLFW itself at least).

@ve-nt
Copy link

ve-nt commented Jan 8, 2025

@asdqwe Ah sorry, didn't see you had updated your comment. Thats very interesting that the coord space is changing 5-7 frames in. I'm going to try and reproduce that on my machine and see if I get the same results, because this isn't something I've noticed so far (which doesn't mean it isnt happening).

@ve-nt
Copy link

ve-nt commented Jan 8, 2025

It appears I'm seeing the same thing you are @asdqwe

Just from a quick test on the core_3d_camera_free.c example, I have:

  • Removed all SetMousePosition calls in DisableCursor
  • Skipped the first 8 frames in the example code

With these two, I'm no longer getting any camera jolt at all. Whether my cursor is over the window or outside the window.

Its not an exact science test as I don't have much time to check this right now, but figured I'd update with this for now.

@ve-nt
Copy link

ve-nt commented Jan 8, 2025

Oh! One thing which is making a difference is your code to initialize Raylib's stored mouse positions in InitPlatform.

I.E. your:

    double xpos, ypos;
    glfwGetCursorPos(platform.handle, &xpos, &ypos);
    CORE.Input.Mouse.previousPosition.x = (float)xpos;
    CORE.Input.Mouse.previousPosition.y = (float)ypos;
    CORE.Input.Mouse.currentPosition.x = (float)xpos;
    CORE.Input.Mouse.currentPosition.y = (float)ypos;

With this in place its working.

I'll clean up my code and put together a branch of exact code thats working for me later on. Hopefully I've not missed anything important and you'll be able to reproduce.

@ve-nt
Copy link

ve-nt commented Jan 8, 2025

Okay screw it I'll just do it now. Here's my working code: ve-nt@fcf2a99

@ve-nt
Copy link

ve-nt commented Jan 8, 2025

No sweat, take all the time you need. Thanks for looking into this.

@asdqwe
Copy link
Contributor

asdqwe commented Jan 8, 2025

Status update (complementary to #4654 (comment)):

  1. The moment when the coords plot change is when the event FocusIn (on Linux) or WM_CAPTURECHANGED | WM_SETFOCUS (on Windows) is triggered, calling disableCursor() (Linux, Windows).

  2. On MacOS I think it happens when there's a NSNotification that will call updateCursorMode.

  3. However, even removing the _glfwCenterCursorInContentArea won't stop the cursor getting centered (see Logs).

  4. So now I'm trying to find where else the GLFW_CURSOR_DISABLED is getting re-centered. And that's the real problem. Because we can force call disableCursor() before the event happen which fixes the mouse coords on those first frames (see the first part of the logs). However, the moment we move the mouse, another "centering" is being applied from somewhere and that's breaking everything (see ">>> mouse move <<<" on the logs).

  5. I'm not manually settting the cursor position yet. Because that second "centering" keeping throwing the cursor position fix out.

Logs:

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

@raysan5
Copy link
Owner

raysan5 commented Jan 8, 2025

@asdqwe wow... really? definitely it seems an issue with GLFW or even the OS windowing system... be careful with that rabbit hole...

EDIT: Afaik, @ColleagueRiley has been dealing with it for RGFW, any ideas?

@ColleagueRiley
Copy link
Contributor

GLFW's handling of things tends to be far more complicated than RGFW's in most cases.

RGFW shouldn't have this problem, so it should be easy to compare the two implementations and see what's different. I'm not really what information is relevant, so here are a few things:

I also made an RGFW article on this issue, not sure if the code is outdated or not:

https://github.com/ColleagueRiley/raw-mouse-input
Here's some of the basic information:

X11 and Windows both have a "raw mouse input mode" that must be enabled. When handling raw mouse input, you must handle specific events,

X11:

	case GenericEvent: {			
		XGetEventData(display, &E.xcookie);
		if (E.xcookie.evtype == XI_RawMotion) {

X11 also requires you to handle MotionNotify events, other wise it will be jumpy.

( GLFW should have simular boolean that can be checked)

		if ((win->_winArgs & RGFW_HOLD_MOUSE)) {
   		/* convert E.xmotion to raw input by subtracting the previous point */
   		win->event.point.x = win->_lastMousePoint.x - E.xmotion.x;
   		win->event.point.y = win->_lastMousePoint.y - E.xmotion.y;
   	}

Windows: You only need to handle the WM_INPUT event.

For cocoa you only need to enable raw input, then you can get the raw mouse point from the usual mouse event. deltaX and deltaY are used to get the raw input.

I'm not 100% sure if GLFW handles Cocoa events directly like this.

CGAssociateMouseAndMouseCursorPosition(0);

I'm not sure if any of this information is helpful,

		case NSEventTypeMouseMoved: {
   		NSPoint p;
   		if ((win->_flags & RGFW_HOLD_MOUSE)) {
   			p.x = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaX"));
   			p.y = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaY"));
   		}

You could also achieve this with IOKit, but this way integrates better if you already use the Cocoa event system.

I hope something here is helpful.

@asdqwe
Copy link
Contributor

asdqwe commented Jan 9, 2025

@raysan5 @ve-nt @alexnivanov, I thought I had found a solution for it, but didn't work out.
Updated #4665 with all information I got. Hopefully someone can fix this issue. Sorry guys.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
external This issue depends on external lib
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants