Slight latency with mouse input

I am trying to implement an object that can be dragged with the mouse cursor, but it seems to very slightly lag behind the cursor when I do.

In earlier versions, this caused a problem where if the cursor and object moved fast enough, it would lag behind so far that they were no longer overlapping, causing the object to “drop” from the cursor. Though I fixed this by improving the “grab” logic, the lag still creates a noticeable visual error; I’ve included a video which shows it. The lag is fairly subtle, but if you look at the position of the mouse, it shifts relative to the object.

Here is my input processing code. Input is a struct that contains booleans for all of the relevant input buttons and floats for mouse position.

void readSdlInput(Input* input) {

	SDL_Event event;
	while (SDL_PollEvent(&event) == 1) {
		/* keyboard input handling */
	}

	SDL_MouseButtonFlags mouseButtons = SDL_GetMouseState(&input->mouseX, &input->mouseY);
	/* mouse button handling */
}

I also attempted an alternative version by swapping out SDL_GetMouseState for SDL_GetGlobalMouseState because I figured maybe synchronous vs. asynchronous communication was somehow responsible for the error. This is what the input processing code looked like, though it seemingly had the same effect.

void readSdlInput(Input* input, SDL_Window* window) {

	SDL_Event event;
	while (SDL_PollEvent(&event) == 1) {
		/* keyboard input handling */
	}

	float32 mouseXGlobal, mouseYGlobal;
	int32 windowX, windowY;
	SDL_GetWindowPosition(window, &windowX, &windowY);
	SDL_MouseButtonFlags mouseButtons = SDL_GetGlobalMouseState(&mouseXGlobal, &mouseYGlobal);
 
	/* mouse button handling */
}

And here is the code that handles movement of the object. When the object is initially grabbed by the cursor, it saves the offset between the cursor and object, and will continuously set the object position to maintain that offset each update.

void updateObjPos(const Input* input, State* state) {

	/* determine if object was grabbed */
	/* if object was just grabbed */
			state->objOffsetX = input->mouseX - state->objX;
			state->objOffsetY = input->mouseY - state->objY;
			state->isObjHeld = true;
	/* if object was already grabbed */
			state->isObjHeld = true;
	/* if object is not being grabbed at all */
			state->isObjHeld = false;

	if (state->isObjHeld) {
		state->objX = input->mouseX - state->objOffsetX;
		state->objY = input->mouseY - state->objOffsetY;
		/* forcefully set objX and objY if they are out of bounds */
	}
}

Both of the above functions are called each update step, with readSdlInput earlier, updateObjPos later, and all graphics last. Ideally, what I want is for the object to move exactly in lockstep with the cursor while being dragged, and I’m not sure how to achieve that.

Basically, the mouse coordinates your app receives are out of date compared to the system mouse cursor shown on screen.

This is because the mouse cursor is drawn as late as possible, with the latest mouse coordinates, to ensure the mouse feels snappy. IIRC on most OSes/hardware it’s drawn by the display controller rather than the GPU, aka when it’s time to send the screen contents to the monitor.

So, because the mouse is moving, by the time your app has finished rendering and its window has been composited with the rest of the screen contents, the mouse cursor location has changed and the display controller has been sent the new, different coordinates.

The solution is to turn the system mouse cursor off (SDL_HideCursor()) and draw your own mouse cursor with SDL_RenderTexture(). Be warned that this can feel noticeably laggy, especially with vsync enabled, which is why the OS doesn’t do it this way!

2 Likes