← All posts

Mouse jittering on Windows using OpenGL

I have been struggling for about a week trying to figure out why, my game was stuttering when the player strafes sideways and rotates the camera in the opposite direction.

See video:

The really weird part was, that it only was behaving like this on Windows builds, but not on Linux or Emscripten builds. It stuttered as well when using WINE to run the Windows build on Linux.

I tried a bunch of things to try to pin point down the issue.

My initial hunch was that something with the mouse inputs was wrong. I’ve had issues in the past with GLWF where the mouse deltas would randomly spike during gameplay, causing the player camera to hitch.

See the following issues on GitHub:

Some of these issues on GitHub are pretty old and haven’t been properly addressed. I tried monkey patching these in my local copy of GLFW and tried to work around it in my engine code, but I couldn’t get it consistently working without breaking a different behavior.

Because of this I switched to SDL2 and the was issue solved for me.

So I assumed it’s going to be something similar for this new jittering issue and I upgraded my windowing library from SDL2 to SDL3, then switched entirely back to GLFW. The issue persisted on all of these.

What didn’t work #

I swapped out my math code with GLM to no avail. I rewrote my game loop to use variable time steps instead of fixed time steps. VSync on/off. Different PC’s. MSAA on/off. Capped the frame rate. Borderless window, exclusive full screen.

Nothing worked.

I went back to my previous projects to check, if maybe it used to work, but I broke something during development. That wasn’t the case. Seemingly all my previous project suffer from the same issue on Windows.

I then created a tiny program with a renderer, that would display the game world with a floating FPS camera. The issue remained. So it wasn’t my actual engine code that caused this.

When researching this issue I saw multiple posts that suggested using raw mouse inputs through the Windows API. I did that. It didn’t work.

It appeared to be a tiny bit smoother, but I could’ve easily halucinated that part, because I wanted this to be over.

What did work #

So apparently due to some Windows specific quirks the frame pacing isn’t as smooth as on Linux. The mouse movement inputs come in more in a burst, rather then continiously.

The “fix” for this issue, and I am not claiming it’s the correct way of handling this, but it works for me and I don’t really care anymore.. is mouse input smoothing.

Clanker summary #

Mouse smoothing (sometimes called “mouse filtering”) in first-person shooters is a system that averages your mouse input over a short time instead of using the raw movement directly.

  1. To reduce jitter on bad hardware
    Older or low-quality mice used to send noisy, inconsistent signals.
    Smoothing helped:
    • Remove micro-shakes
    • Make camera movement look less “twitchy”

  2. To make movement feel more cinematic
    Developers sometimes want motion to feel:
    • Fluid
    • Less robotic
    • More controller-like
      This is especially common in single-player or console-focused shooters.

  3. To hide frame rate/input inconsistencies
    If a game has uneven frame timing, smoothing can mask:
    • Micro stutters
    • Input spikes

The fix #

const auto& mouse_state = inputs.GetMouseState();
const float msens = UserConfig::cfg_mouse_sensitivity.GetFloat();
const Vec2 raw_input_rot = {
	mouse_state.delta.y * msens,
	mouse_state.delta.x * msens
};
const float smoothing_hz = std::max( 0.0f, UserConfig::cfg_mouse_smoothing.GetFloat() );
const float smoothing = expf( -frame_time * smoothing_hz );
const float alpha = 1.0f - smoothing;

// smoothed_input_rot is a member variable.
smoothed_input_rot.x = smoothed_input_rot.x * smoothing + raw_input_rot.x * alpha;
smoothed_input_rot.y = smoothed_input_rot.y * smoothing + raw_input_rot.y * alpha;
- smoothing_hz must be > 0
- higher smoothing_hz -> faster response, less smoothing
- lower smoothing_hz -> slower response, more smoothing/lag

So instead of jumping straight from “mouse delta t0” to “mouse delta t1”, it interpolates smoothly using the exponential curve.

I hope this helps you #