SDL2跟随鼠标位置有延迟

3

我尝试做一个简单的矩形跟随鼠标移动。无论我使用什么方法获取鼠标位置,都存在延迟。起初并不是很烦人,直到需要进行非线性运动或快速移动时。

以下是我运行测试代码的全部内容,即使在最简单的设置下也会出现延迟。以下代码不带fps限制,因此效果稍微好一些,但您仍然可以看到延迟。我认为这并不是硬件问题,因为:

  • 我有一台相当好的计算机;
  • 我发现很多旧话题与我的问题相同,大多数未解决或似乎已有答案,但对我不起作用。

我发现的大多数答案是“关闭v-sync / fps cap”,我不想这样做。

真的没有办法让它工作吗?为什么会存在这个延迟?如果由于fps上限而导致方块的移动不流畅,我会理解,但为什么他们会落后于鼠标位置,而不是直接“瞬移到”鼠标位置呢?

#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;
}

听起来像是硬件光标渲染。通过鼠标轨迹的hack手段强制使用软件光标渲染是否有帮助?或者,使用SDL_ShowCursor(SDL_DISABLE) + 自绘光标图像/框? - genpfault
不确定更改注册表的用途,但稍后我会尝试; SDL绘制的光标不会改变任何东西,因为获取用户鼠标位置是错误的。绘制一个只会使光标感觉更加迟缓/不响应。 谢谢您的答案。 - Jabob
2个回答

0

有几个问题...

  1. 混合使用Windows API调用(用于鼠标位置)可能是问题的一部分。忘记winAPI调用,只使用SDL鼠标位置。
  2. 我在Linux上测试了你的程序,所以我不得不删除winAPI的内容。结果似乎还可以。
  3. 在渲染后,你没有重置endTicks。所以,在第一次之后,它将在每个外部循环中被调用。因此,它会对渲染器造成压力。
  4. 最好在渲染块之外执行事件循环。
  5. 据我所知,没有必要重新获取鼠标位置。上一个运动事件的最后位置就足够了。

我制作了几个版本来逐步展示修复:

  1. 只需删除winAPI调用。
  2. 将事件循环移到渲染if块之外,并正确设置endTicks
  3. 只使用上一个运动事件的鼠标位置。
  4. 最终的、完全清理过的版本(不包括#if 0)。

在下面的代码中,我使用cpp条件语句来表示旧代码与新代码(例如):
#if 0
// old code
#else
// new code
#endif

#if 1
// new code
#endif

(1)这是重构后的代码。这只是删除了winAPI调用:

#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)这是一个包含我提到的大部分其他修复(例如正确设置endTicks)的版本。它仍然使用SDL_GetMouseState。看起来更加流畅一些。
#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)这里有一个版本,它只使用了上一个鼠标移动事件的鼠标位置:
#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)一个完全清理过的版本:

#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;
}

也许是我表达不够清楚:1 - WinAPI 是因为这是一个测试文件,删除它不会有任何变化,但你可以看到 SDL 和 Windows API 没有区别。2 - endTicks 被注释掉是因为代码块中没有“fps cap”,这是 endTicks 的唯一目的。3 - 至于(1),调用了 SDL_GetMouseState 来比较三种方法。将 EventHandler 移出循环似乎没有改变什么(或者改变非常小,我看不出来)。遗憾的是,你提供的解决方案都没有对我起作用。也许是 Windows 的问题?或者软件的问题?感谢你的回答。 - Jabob

0

除了其他所有事情之外,从SDL_PollEvents(&EventHandler)切换到SDL_WaitEvents(&EventHandler)。这是一个非常重要的提升,因为您需要广泛使用鼠标。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接