3

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;
}
genpfault
  • 51,148
  • 11
  • 85
  • 139
Jabob
  • 31
  • 2
  • Sounds like [hardware cursor rendering](https://stackoverflow.com/questions/6957039). Does [forcing software cursor rendering via mouse-trails hackery](https://photo.stackexchange.com/a/30655) help? Alternatively, `SDL_ShowCursor(SDL_DISABLE)` + self-drawn cursor image/box? – genpfault Mar 07 '22 at 17:06
  • Not sure what would be the use of changing the regedit but I'll try later; SDL drawn cursor wouldn't change a thing as getting user's mouse position is off. Drawing one would just give the cursor an even more pronounced feeling of sluggishness/unresponsiveness. Thank you for the answer tho. – Jabob Mar 08 '22 at 00:21

2 Answers2

0

A few issues ...

  1. Intermixing windows API calls (for mouse position) may be part of the problem. Forget the winAPI calls and just use the SDL mouse position.
  2. I tested your program on linux, so I had to remove the winAPI stuff. The result seemed to be "okay"
  3. You do not reset endTicks after doing a render. So, after the first time, it will be called on every outer loop. So, it is hammering the renderer.
  4. Better to do the event loop outside of the rendering block.
  5. 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:

  1. Just remove winAPI calls.
  2. Move event loop outside of the render if block and set endTicks correctly.
  3. Just use the mouse position from the last motion event.
  4. 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;
}
Craig Estey
  • 30,627
  • 4
  • 24
  • 48
  • Maybe a bit of miscommunication from me: 1 - WinAPI is because it's a test file, removing it changes nothing, but you can see that SDL and Windows api doesn't make a difference. 2 - endTicks was commented out because the chunck of code was "fps cap"-free, which is the sole purpose of endTicks. 3 - As for (1), SDL_GetMouseState was called to compare the three methods. Moving EventHandler outside of loop seems to change nothing (or it's so little that I can't see it). Sadly none of your solutions worked for me. Maybe a windows thing? Or software? Thanks for the answer tho. – Jabob Mar 07 '22 at 00:42
0

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.

AggelosT
  • 108
  • 1
  • 9