Skip to content

Reported mouse movement is massively slower on Wasm than on native when cursor is locked #18855

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

Open
janhohenheim opened this issue Apr 15, 2025 · 3 comments
Labels
A-Input Player input via keyboard, mouse, gamepad, and more A-Windowing Platform-agnostic interface layer to run your app in C-Bug An unexpected or incorrect behavior O-Web Specific to web (WASM) builds S-Blocked This cannot move forward until something else changes

Comments

@janhohenheim
Copy link
Member

janhohenheim commented Apr 15, 2025

Bevy version

0.15.3 and 0.16.0-rc.5

Relevant system information

native:

2025-04-15T20:34:25.405810Z  INFO bevy_diagnostic::system_information_diagnostics_plugin::internal: SystemInfo { os: "Linux 41 Fedora Linux", kernel: "6.13.9-200.fc41.x86_64", cpu: "AMD Ryzen 9 7950X 16-Core Processor", core_count: "16", memory: "30.4 GiB" }
2025-04-15T20:34:25.466728Z  INFO bevy_render::renderer: AdapterInfo { name: "AMD Radeon RX 7900 XTX (RADV NAVI31)", vendor: 4098, device: 29772, device_type: DiscreteGpu, driver: "radv", driver_info: "Mesa 25.0.2", backend: Vulkan }

firefox:

INFO /home/hhh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_render-0.15.3/src/renderer/mod.rs:199 AdapterInfo { name: "Radeon R9 200 Series, or similar", vendor: 4098, device: 0, device_type: Other, driver: "", driver_info: "WebGL 2.0", backend: Gl }

chromium:

INFO /home/hhh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_render-0.16.0-rc.5/src/renderer/mod.rs:200 AdapterInfo { name: "ANGLE (AMD, AMD Radeon RX 7900 XTX (radeonsi navi31 LLVM 19.1.7), OpenGL 4.6)", vendor: 4098, device: 0, device_type: Other, driver: "", driver_info: "WebGL 2.0 (OpenGL ES 3.0 Chromium)", backend: Gl }

Firefox: 137.0 (64-bit)
Chromium: Version 135.0.7049.84 (Official Build) (64-bit)

What you did

Either execute this Bevy playground snippet directly or write this code:

main.rs
use std::time::Duration;

use bevy::{
    input::{common_conditions::input_just_pressed, mouse::AccumulatedMouseMotion},
    prelude::*,
    window::CursorGrabMode,
};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(
            Update,
            (
                count_motion,
                capture_cursor.run_if(input_just_pressed(MouseButton::Left)),
            ),
        )
        .run();
}

fn count_motion(
    time: Res<Time>,
    accumulated_mouse_motion: Res<AccumulatedMouseMotion>,
    mut motion_per_update: Local<Vec<f32>>,
    mut timer: Local<Option<Timer>>,
) {
    let timer = timer.get_or_insert(Timer::new(Duration::from_secs(1), TimerMode::Repeating));
    let delta = accumulated_mouse_motion.delta;
    motion_per_update.push(delta.length());
    if timer.tick(time.delta()).finished() {
        let total_move_distance = motion_per_update.iter().sum::<f32>();
        let max_motion = motion_per_update
            .iter()
            .copied()
            .max_by(|a, b| a.partial_cmp(b).unwrap())
            .unwrap_or(0.0);
        let count = motion_per_update.len();
        let average = total_move_distance / count as f32;
        let median = median(&motion_per_update);
        let zero_count = motion_per_update.iter().filter(|&x| *x == 0.0).count();
        info!(
            "total move distance this second: {total_move_distance} (n: {count}, n zeros: {zero_count}, max: {max_motion}, average: {average}, median: {median})"
        );
        motion_per_update.clear();
    }
}

fn median(values: &[f32]) -> f32 {
    let mut sorted = values.to_vec();
    sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
    sorted[sorted.len() / 2]
}

fn capture_cursor(mut window: Single<&mut Window>) {
    window.cursor_options.visible = false;
    window.cursor_options.grab_mode = CursorGrabMode::Locked;
}

Now run it once using bevy run and once using bevy run web. On web, open the console to see the output.

What went wrong

On Firefox:
Move the mouse around the window in web before you click into the window. Notice the output.
Now, click into the center of the window to lock the cursor. Continue moving the mouse at the same pace. Notice that the output drastically went down.

On native, you will not be able to recreate this behavior.
For me, the bug was not present on Chromium, but other users report getting the issue only on Chrome.

Additional information

Using an extra scaling factor on Wasm is not enough, as slow mouse movements get reported as absolute zero.
This might also very well be a winit bug, I didn't investigate it independently of Bevy.

This is the output for slow mouse movement:

Image
The cutoff to the locked state should be obvious, as the numbers go down drastically.
Note that the message saying that the mouse moved a distance of zero is collapsed as it is repeated 6(!) times.

This is the output for fast mouse movement:
Image
Note that the amount of zeros does not go up, but the maximal distance and average distance per second are drastically lower.

@janhohenheim janhohenheim added A-Input Player input via keyboard, mouse, gamepad, and more C-Bug An unexpected or incorrect behavior O-Web Specific to web (WASM) builds A-Windowing Platform-agnostic interface layer to run your app in labels Apr 15, 2025
@janhohenheim janhohenheim changed the title Mouse movement is slower on Wasm than on native when cursor is locked Reported mouse movement is massively slower on Wasm than on native when cursor is locked Apr 15, 2025
@janhohenheim janhohenheim changed the title Reported mouse movement is massively slower on Wasm than on native when cursor is locked Reported mouse movement is massively slower on Wasm on Firefox than on native when cursor is locked Apr 15, 2025
@janhohenheim
Copy link
Member Author

janhohenheim commented Apr 15, 2025

Note that this issue means that first-person games are effectively currently unplayable on my Firefox when using a low mouse sensitivity.

@janhohenheim janhohenheim changed the title Reported mouse movement is massively slower on Wasm on Firefox than on native when cursor is locked Reported mouse movement is massively slower on Wasm than on native when cursor is locked Apr 15, 2025
@dsgallups
Copy link

dsgallups commented Apr 15, 2025

I've got the reverse issue.

Info:

Apple M4 Max
macOS 15.4

Firefox 137.0.1
Chrome 135.0.7049.85

Edit: video won't upload properly. Video located in discord
https://discord.com/channels/691052431525675048/691052431974465548/1361836162855080090

Output:
Chrome: 80
Firefox: 162
Native: ~77

Wrote a script to reproduce

use device_query::{DeviceQuery, DeviceState, Keycode};
use enigo::*;
use std::{thread, time::Duration};

fn main() {
    let device_state = DeviceState::new();

    let mut enigo = Enigo::new(&Settings::default()).unwrap();

    let mut toggled = false;
    let mut k_was_down = false;

    println!("Press 'K' to toggle mouse movement ON/OFF.");
    println!("Press Ctrl+C to exit.");

    loop {
        let keys = device_state.get_keys();

        let k_down = keys.contains(&Keycode::K);

        if k_down && !k_was_down {
            toggled = !toggled;
            println!("Toggled: {}", toggled);
        }
        k_was_down = k_down;

        if toggled {
            // Move the mouse 1 pixel to the right
            _ = enigo.move_mouse(1, 0, Coordinate::Rel);
        }

        thread::sleep(Duration::from_millis(10));
    }
}

@jf908
Copy link
Contributor

jf908 commented Apr 16, 2025

Unfortunately I don't think there's anything to be done from bevy's side to resolve this because its caused by browser bugs in the Pointer Lock API.

Some issues that may explain these effects:

  1. Firefox works in a different mouse movement coordinate system, explained in this winit comment.
  2. Chrome supports a unadjustedMovement flag to get mouse input without OS sensitivity scaling. This setting is optimal for first person games since most native FPS games also use raw mouse input. Without setting this flag, there's a bug where the mouse movement can teleport as it wraps around your screen. There's currently no way to set this flag in bevy without manually changing the source code but it will be the default behaviour in the currently unreleased Winit 0.31.
  3. Pointer lock can behave differently depending on both your OS and browser.

This demo page is a nice sanity check to make sure that its the browser acting weird and not your own code.

@janhohenheim janhohenheim added the S-Blocked This cannot move forward until something else changes label Apr 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Input Player input via keyboard, mouse, gamepad, and more A-Windowing Platform-agnostic interface layer to run your app in C-Bug An unexpected or incorrect behavior O-Web Specific to web (WASM) builds S-Blocked This cannot move forward until something else changes
Projects
None yet
Development

No branches or pull requests

3 participants