'SDL2 following mouse position has a delay
I'm trying to do a simple rectangle following the mouse. No matter what I use to get mouse position, there is a delay. Not really annoying at first until you need to do non-linear movement or quick movements.
The following code is the entirety of the code I'm running to test it, that delay appear even in the most barebone setting. Following code is fps-uncapped so it's less worse, but you can still see the delay. I don't think it's hardware related as:
- I have a pretty good computer;
- I found A LOT of old topics about the same issue as me, most unresolved or seems to have had an answer that doesn't work for me.
Most answers I've found were "turn off v-sync/fps cap" which I would like to not do.
Is there really no way to make it work? Why does that delay exist? I would understand if the boxes' movement weren't smooth because of the fps cap, but why are they lagging behind instead of just 'teleporting' to mouse position?
#include <windows.h>
#include "SDL2/SDL.h"
#define ARRAY_CONSINT (const int[])
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
void drawRectangle( SDL_Renderer *renderer, SDL_Rect rect, const int clr[], int fill)
{
SDL_SetRenderDrawColor(renderer, clr[0], clr[1], clr[2], clr[3]);
if ( fill == 0)
SDL_RenderDrawRect(renderer, &rect);
else
SDL_RenderFillRect(renderer, &rect);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
}
int main( int argc, char* args[])
{
struct Mouse_s{
int X;
int Y;
};
int lQuit;
POINT Windows_Mouse;
SDL_Window *gWindow;
SDL_Surface *screenSurface;
SDL_Renderer *renderer;
Uint32 startTicks;
Uint32 endTicks;
Uint32 DeltaTime;
int showFPS;
struct Mouse_s SDL_Mouse;
struct Mouse_s Motion_Mouse;
lQuit = 0;
SDL_Mouse.X = 0;
SDL_Mouse.Y = 0;
Motion_Mouse.X = 0;
Motion_Mouse.Y = 0;
startTicks = 0;
endTicks = 0;
DeltaTime = 0;
SDL_Init( SDL_INIT_EVERYTHING );
gWindow = SDL_CreateWindow( "Window", -1, -1, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
renderer = SDL_CreateRenderer( gWindow, -1, SDL_RENDERER_ACCELERATED ); // | SDL_RENDERER_PRESENTVSYNC
screenSurface = SDL_GetWindowSurface( gWindow );
SDL_UpdateWindowSurface( gWindow );
while ( lQuit == 0 )
{
startTicks = SDL_GetTicks();
DeltaTime = startTicks - endTicks;
if ( DeltaTime > 1000/60.0 )
{
//endTicks = SDL_GetTicks();
SDL_RenderClear(renderer);
SDL_Event EventHandler;
while( SDL_PollEvent( &EventHandler ) != 0)
{
if( EventHandler.type == SDL_QUIT )
lQuit = 1;
else if ( EventHandler.type == SDL_MOUSEMOTION )
{
Motion_Mouse.X = EventHandler.motion.x;
Motion_Mouse.Y = EventHandler.motion.y;
}
}
SDL_GetMouseState(&SDL_Mouse.X, &SDL_Mouse.Y);
const SDL_Rect rect = {SDL_Mouse.X,SDL_Mouse.Y-50,100,50};
drawRectangle( renderer, rect, ARRAY_CONSINT{255,0,0,255}, 0);
GetCursorPos(&Windows_Mouse);
const SDL_Rect rect2 = {Windows_Mouse.x,Windows_Mouse.y-50,70,40};
drawRectangle( renderer, rect2, ARRAY_CONSINT{0,255,0,255}, 0);
const SDL_Rect rect3 = {Motion_Mouse.X,Motion_Mouse.Y-50,40,30};
drawRectangle( renderer, rect3, ARRAY_CONSINT{0,0,255,255}, 0);
SDL_RenderPresent(renderer);
}
}
return 0;
}
Solution 1:[1]
A few issues ...
- Intermixing windows API calls (for mouse position) may be part of the problem. Forget the winAPI calls and just use the SDL mouse position.
- I tested your program on linux, so I had to remove the winAPI stuff. The result seemed to be "okay"
- You do not reset
endTicksafter doing a render. So, after the first time, it will be called on every outer loop. So, it is hammering the renderer. - Better to do the event loop outside of the rendering block.
- AFAICT, there is no need to (re)get the mouse position. The last position from the the last motion event is sufficient.
I've produced a few versions to progressively show the fixes:
- Just remove winAPI calls.
- Move event loop outside of the render
ifblock and setendTickscorrectly. - Just use the mouse position from the last motion event.
- Final, fully cleaned up version (without
#if 0).
In the code below, I use cpp conditionals to denote old vs. new code (e.g):
#if 0
// old code
#else
// new code
#endif
#if 1
// new code
#endif
(1) Here is the refactored code. This just has removal of the winAPI calls:
#if 0
#include <windows.h>
#endif
#include "SDL2/SDL.h"
#define ARRAY_CONSINT (const int[])
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
void
drawRectangle(SDL_Renderer *renderer, SDL_Rect rect, const int clr[], int fill)
{
SDL_SetRenderDrawColor(renderer, clr[0], clr[1], clr[2], clr[3]);
if (fill == 0)
SDL_RenderDrawRect(renderer, &rect);
else
SDL_RenderFillRect(renderer, &rect);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
}
int
main(int argc, char *args[])
{
struct Mouse_s {
int X;
int Y;
};
int lQuit;
#if 0
POINT Windows_Mouse;
#endif
SDL_Window *gWindow;
SDL_Surface *screenSurface;
SDL_Renderer *renderer;
Uint32 startTicks;
Uint32 endTicks;
Uint32 DeltaTime;
int showFPS;
struct Mouse_s SDL_Mouse;
struct Mouse_s Motion_Mouse;
lQuit = 0;
SDL_Mouse.X = 0;
SDL_Mouse.Y = 0;
Motion_Mouse.X = 0;
Motion_Mouse.Y = 0;
startTicks = 0;
endTicks = 0;
DeltaTime = 0;
SDL_Init(SDL_INIT_EVERYTHING);
gWindow = SDL_CreateWindow("Window", -1, -1, SCREEN_WIDTH, SCREEN_HEIGHT,
SDL_WINDOW_SHOWN);
#if 0
renderer = SDL_CreateRenderer(gWindow, -1,
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
#else
renderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_ACCELERATED);
#endif
screenSurface = SDL_GetWindowSurface(gWindow);
SDL_UpdateWindowSurface(gWindow);
while (lQuit == 0) {
startTicks = SDL_GetTicks();
DeltaTime = startTicks - endTicks;
if (DeltaTime > 1000 / 60.0) {
// endTicks = SDL_GetTicks();
SDL_RenderClear(renderer);
SDL_Event EventHandler;
while (SDL_PollEvent(&EventHandler) != 0) {
if (EventHandler.type == SDL_QUIT)
lQuit = 1;
else if (EventHandler.type == SDL_MOUSEMOTION) {
Motion_Mouse.X = EventHandler.motion.x;
Motion_Mouse.Y = EventHandler.motion.y;
}
}
SDL_GetMouseState(&SDL_Mouse.X, &SDL_Mouse.Y);
const SDL_Rect rect = { SDL_Mouse.X, SDL_Mouse.Y - 50, 100, 50 };
drawRectangle(renderer, rect, ARRAY_CONSINT {
255, 0, 0, 255}, 0);
#if 0
GetCursorPos(&Windows_Mouse);
const SDL_Rect rect2 = { Windows_Mouse.x, Windows_Mouse.y - 50,
70, 40 };
#else
const SDL_Rect rect2 = { SDL_Mouse.X, SDL_Mouse.Y - 50, 70, 40 };
#endif
drawRectangle(renderer, rect2, ARRAY_CONSINT {
0, 255, 0, 255}, 0);
const SDL_Rect rect3 = { Motion_Mouse.X, Motion_Mouse.Y - 50,
40, 30 };
drawRectangle(renderer, rect3, ARRAY_CONSINT {
0, 0, 255, 255}, 0);
SDL_RenderPresent(renderer);
}
}
return 0;
}
(2) Here is a version with most of the other fixes I mentioned (e.g. setting endTicks correctly). It still does SDL_GetMouseState. It seems to be a bit smoother:
#if 0
#include <windows.h>
#endif
#include "SDL2/SDL.h"
#define ARRAY_CONSINT (const int[])
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
void
drawRectangle(SDL_Renderer * renderer, SDL_Rect rect, const int clr[], int fill)
{
SDL_SetRenderDrawColor(renderer, clr[0], clr[1], clr[2], clr[3]);
if (fill == 0)
SDL_RenderDrawRect(renderer, &rect);
else
SDL_RenderFillRect(renderer, &rect);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
}
int
main(int argc, char *args[])
{
struct Mouse_s {
int X;
int Y;
};
int lQuit;
#if 0
POINT Windows_Mouse;
#endif
SDL_Window *gWindow;
SDL_Surface *screenSurface;
SDL_Renderer *renderer;
Uint32 startTicks;
Uint32 endTicks;
Uint32 DeltaTime;
int showFPS;
struct Mouse_s SDL_Mouse;
struct Mouse_s Motion_Mouse;
lQuit = 0;
SDL_Mouse.X = 0;
SDL_Mouse.Y = 0;
Motion_Mouse.X = 0;
Motion_Mouse.Y = 0;
startTicks = 0;
endTicks = 0;
DeltaTime = 0;
SDL_Init(SDL_INIT_EVERYTHING);
gWindow = SDL_CreateWindow("Window", -1, -1, SCREEN_WIDTH, SCREEN_HEIGHT,
SDL_WINDOW_SHOWN);
#if 0
renderer = SDL_CreateRenderer(gWindow, -1,
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
#else
renderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_ACCELERATED);
#endif
screenSurface = SDL_GetWindowSurface(gWindow);
SDL_UpdateWindowSurface(gWindow);
while (lQuit == 0) {
// NOTE/FIX: do this on every loop
#if 1
SDL_Event EventHandler;
while (SDL_PollEvent(&EventHandler) != 0) {
if (EventHandler.type == SDL_QUIT)
lQuit = 1;
else if (EventHandler.type == SDL_MOUSEMOTION) {
Motion_Mouse.X = EventHandler.motion.x;
Motion_Mouse.Y = EventHandler.motion.y;
}
}
#endif
startTicks = SDL_GetTicks();
DeltaTime = startTicks - endTicks;
if (DeltaTime > 1000 / 60.0) {
// NOTE/FIX: set endTicks to prevent _excessive_ rendering
#if 1
endTicks = startTicks;
#endif
SDL_RenderClear(renderer);
// NOTE/BUG: do this _outside_ the rendering time and do _not_ do it after
// the render clear
#if 0
SDL_Event EventHandler;
while (SDL_PollEvent(&EventHandler) != 0) {
if (EventHandler.type == SDL_QUIT)
lQuit = 1;
else if (EventHandler.type == SDL_MOUSEMOTION) {
Motion_Mouse.X = EventHandler.motion.x;
Motion_Mouse.Y = EventHandler.motion.y;
}
}
#endif
SDL_GetMouseState(&SDL_Mouse.X, &SDL_Mouse.Y);
const SDL_Rect rect = { SDL_Mouse.X, SDL_Mouse.Y - 50, 100, 50 };
drawRectangle(renderer, rect, ARRAY_CONSINT {
255, 0, 0, 255}, 0);
#if 0
GetCursorPos(&Windows_Mouse);
const SDL_Rect rect2 = { Windows_Mouse.x, Windows_Mouse.y - 50,
70, 40 };
#else
const SDL_Rect rect2 = { SDL_Mouse.X, SDL_Mouse.Y - 50, 70, 40 };
#endif
drawRectangle(renderer, rect2, ARRAY_CONSINT {
0, 255, 0, 255}, 0);
const SDL_Rect rect3 = { Motion_Mouse.X, Motion_Mouse.Y - 50,
40, 30 };
drawRectangle(renderer, rect3, ARRAY_CONSINT {
0, 0, 255, 255}, 0);
SDL_RenderPresent(renderer);
}
}
return 0;
}
(3) Here is a version that just uses the mouse position from the last motion event:
#if 0
#include <windows.h>
#endif
#include "SDL2/SDL.h"
#define ARRAY_CONSINT (const int[])
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
void
drawRectangle(SDL_Renderer * renderer, SDL_Rect rect, const int clr[], int fill)
{
SDL_SetRenderDrawColor(renderer, clr[0], clr[1], clr[2], clr[3]);
if (fill == 0)
SDL_RenderDrawRect(renderer, &rect);
else
SDL_RenderFillRect(renderer, &rect);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
}
int
main(int argc, char *args[])
{
struct Mouse_s {
int X;
int Y;
};
int lQuit;
#if 0
POINT Windows_Mouse;
#endif
SDL_Window *gWindow;
SDL_Surface *screenSurface;
SDL_Renderer *renderer;
Uint32 startTicks;
Uint32 endTicks;
Uint32 DeltaTime;
int showFPS;
#if 0
struct Mouse_s SDL_Mouse;
#endif
struct Mouse_s Motion_Mouse;
lQuit = 0;
#if 0
SDL_Mouse.X = 0;
SDL_Mouse.Y = 0;
#endif
Motion_Mouse.X = 0;
Motion_Mouse.Y = 0;
startTicks = 0;
endTicks = 0;
DeltaTime = 0;
SDL_Init(SDL_INIT_EVERYTHING);
gWindow = SDL_CreateWindow("Window", -1, -1, SCREEN_WIDTH, SCREEN_HEIGHT,
SDL_WINDOW_SHOWN);
#if 0
renderer = SDL_CreateRenderer(gWindow, -1,
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
#else
renderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_ACCELERATED);
#endif
screenSurface = SDL_GetWindowSurface(gWindow);
SDL_UpdateWindowSurface(gWindow);
while (lQuit == 0) {
// NOTE/FIX: do this on every loop
#if 1
SDL_Event EventHandler;
while (SDL_PollEvent(&EventHandler) != 0) {
if (EventHandler.type == SDL_QUIT)
lQuit = 1;
else if (EventHandler.type == SDL_MOUSEMOTION) {
Motion_Mouse.X = EventHandler.motion.x;
Motion_Mouse.Y = EventHandler.motion.y;
}
}
#endif
startTicks = SDL_GetTicks();
DeltaTime = startTicks - endTicks;
if (DeltaTime > 1000 / 60.0) {
// NOTE/FIX: set endTicks to prevent _excessive_ rendering
#if 1
endTicks = startTicks;
#endif
SDL_RenderClear(renderer);
// NOTE/BUG: do this _outside_ the rendering time and do _not_ do it after
// the render clear
#if 0
SDL_Event EventHandler;
while (SDL_PollEvent(&EventHandler) != 0) {
if (EventHandler.type == SDL_QUIT)
lQuit = 1;
else if (EventHandler.type == SDL_MOUSEMOTION) {
Motion_Mouse.X = EventHandler.motion.x;
Motion_Mouse.Y = EventHandler.motion.y;
}
}
#endif
// NOTE/BUG: no need to reget mouse position -- the motion event has it
#if 0
SDL_GetMouseState(&SDL_Mouse.X, &SDL_Mouse.Y);
#endif
const SDL_Rect rect = { Motion_Mouse.X, Motion_Mouse.Y - 50,
100, 50 };
drawRectangle(renderer, rect, ARRAY_CONSINT {
255, 0, 0, 255}, 0);
#if 0
GetCursorPos(&Windows_Mouse);
const SDL_Rect rect2 = { Windows_Mouse.x, Windows_Mouse.y - 50,
70, 40 };
#else
const SDL_Rect rect2 = { Motion_Mouse.X, Motion_Mouse.Y - 50,
70, 40 };
#endif
drawRectangle(renderer, rect2, ARRAY_CONSINT {
0, 255, 0, 255}, 0);
const SDL_Rect rect3 = { Motion_Mouse.X, Motion_Mouse.Y - 50,
40, 30 };
drawRectangle(renderer, rect3, ARRAY_CONSINT {
0, 0, 255, 255}, 0);
SDL_RenderPresent(renderer);
}
}
return 0;
}
(4) A fully cleaned up version:
#include "SDL2/SDL.h"
#define ARRAY_CONSINT (const int[])
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
void
drawRectangle(SDL_Renderer *renderer, SDL_Rect rect, const int clr[], int fill)
{
SDL_SetRenderDrawColor(renderer, clr[0], clr[1], clr[2], clr[3]);
if (fill == 0)
SDL_RenderDrawRect(renderer, &rect);
else
SDL_RenderFillRect(renderer, &rect);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
}
int
main(int argc, char *args[])
{
struct Mouse_s {
int X;
int Y;
};
int lQuit;
SDL_Window *gWindow;
SDL_Surface *screenSurface;
SDL_Renderer *renderer;
Uint32 startTicks;
Uint32 endTicks;
Uint32 DeltaTime;
int showFPS;
struct Mouse_s Motion_Mouse;
lQuit = 0;
Motion_Mouse.X = 0;
Motion_Mouse.Y = 0;
startTicks = 0;
endTicks = 0;
DeltaTime = 0;
SDL_Init(SDL_INIT_EVERYTHING);
gWindow = SDL_CreateWindow("Window", -1, -1, SCREEN_WIDTH, SCREEN_HEIGHT,
SDL_WINDOW_SHOWN);
renderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_ACCELERATED);
screenSurface = SDL_GetWindowSurface(gWindow);
SDL_UpdateWindowSurface(gWindow);
while (lQuit == 0) {
SDL_Event EventHandler;
while (SDL_PollEvent(&EventHandler) != 0) {
if (EventHandler.type == SDL_QUIT)
lQuit = 1;
else if (EventHandler.type == SDL_MOUSEMOTION) {
Motion_Mouse.X = EventHandler.motion.x;
Motion_Mouse.Y = EventHandler.motion.y;
}
}
startTicks = SDL_GetTicks();
DeltaTime = startTicks - endTicks;
if (DeltaTime > 1000 / 60.0) {
endTicks = startTicks;
SDL_RenderClear(renderer);
const SDL_Rect rect = { Motion_Mouse.X, Motion_Mouse.Y - 50,
100, 50 };
drawRectangle(renderer, rect, ARRAY_CONSINT {
255, 0, 0, 255}, 0);
const SDL_Rect rect2 = { Motion_Mouse.X, Motion_Mouse.Y - 50,
70, 40 };
drawRectangle(renderer, rect2, ARRAY_CONSINT {
0, 255, 0, 255}, 0);
const SDL_Rect rect3 = { Motion_Mouse.X, Motion_Mouse.Y - 50,
40, 30 };
drawRectangle(renderer, rect3, ARRAY_CONSINT {
0, 0, 255, 255}, 0);
SDL_RenderPresent(renderer);
}
}
return 0;
}
Solution 2:[2]
Along with everything else, switch from the SDL_PollEvents(&EventHandler) to SDL_WaitEvents(&EventHandler). This is a really important boost since you work with the mouse extensively.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | AggelosT |
