I’ve been trying to eliminate some stuttering on a game when using high frame rates, e.g. 60fps. After much research trying to improve timing and the game loop, I finally realised the game hadn’t missed a beat. Every single update of the game logic was followed by a rendering update. Then I tried playing with SDL flags. If I use SDL_WINDOW_FULLSCREEN_DESKTOP, there is stuttering. If I use SDL_WINDOW_FULLSCREEN with a lower resolution than native, NO stuttering. Also, no fullscreen at all also results in NO stuttering.
I’m using SDL_RenderPresent. I don’t see this problem with SDL_WindowUpdateSurface. But I need hardware acceleration, so I use a renderer. If I set the renderer to software, the stuttering is still there.
The effect can be seen in both Linux and Windows with the same code. Using SDL_WINDOW_FULLSCREEN and native resolution also results in stuttering. I’ve reduced the game down to just the game loop, and still the stuttering persists.
Incidentally, the stuttering always starts approx 2 seconds after starting the program. Mostly, the stutter will be just once, but about 5% (or less) of the time it will stutter continuously for the entire duration of execution.
This is noticeable on anything moving fast at small increments. Anything slow or large increments is not noticeable.
Why does it only happen when using native resolution?
// stutter test
#include <SDL2/SDL.h>
enum
{
GS_RUN,
GS_QUIT
};
int gameState = GS_RUN;
#ifdef _WIN32
int WinMain()
#else
int main()
#endif
{
SDL_Event event;
SDL_Window *sdlWindow;
SDL_Renderer *sdlRenderer;
SDL_Rect sdlRect = {0, 0, 0, 0};
SDL_DisplayMode mode;
int width, height;
int windowFlags = 0;
// using harware acceleration gives the same results
// SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
int rendererFlags = SDL_RENDERER_SOFTWARE;
int rectSize, rectPos = 0, rectSpeed;
double TICKRATE;
double ticks;
int current;
int update = 0;
int count1 = 0, count2 = 0;
SDL_Init(SDL_INIT_VIDEO);
SDL_GetDesktopDisplayMode(0, &mode);
#if 1 // this gives a fullscreen mode
width = mode.w;
height = mode.h;
windowFlags = SDL_WINDOW_FULLSCREEN_DESKTOP;
#else // this one for windowed
width = 800;
height = 600;
#endif
rectSize = sdlRect.w = sdlRect.h = height / 20;
sdlRect.y = (height - rectSize) / 2;
rectSpeed = width / rectSize / 10;
// this is to set fps to monitor Hz rate
// nothing to do with VSYNC
TICKRATE = 1000.0 / (double)mode.refresh_rate;
sdlWindow = SDL_CreateWindow("Test",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
width, height, windowFlags);
sdlRenderer = SDL_CreateRenderer(sdlWindow, -1,
rendererFlags);
ticks = SDL_GetTicks();
// game loop
while (gameState != GS_QUIT)
{
current = SDL_GetTicks();
// update loop
while (ticks + TICKRATE <= current)
{
update = 1; // update the renderer
count1++; // count how many times we've been thru this loop
ticks += TICKRATE;
rectPos += rectSpeed;
if (rectPos >= width - rectSize)
{
gameState = GS_QUIT;
break;
}
}
// event loop blah blah
while (SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT)
gameState = GS_QUIT;
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)
gameState = GS_QUIT;
}
if (update)
{
SDL_SetRenderDrawColor(sdlRenderer, 0xff, 0xff, 0xff, 0xff);
SDL_RenderClear(sdlRenderer);
SDL_SetRenderDrawColor(sdlRenderer, 0x00, 0x00, 0x00, 0xff);
sdlRect.x = rectPos;
SDL_RenderFillRect(sdlRenderer, &sdlRect);
SDL_RenderPresent(sdlRenderer);
update = 0;
if (count1 > 0) // start the counters together
count2++; // count how many times we've updated the renderer
// move the printf to here to show the counters stay in sync
// the entire duration of execution
}
}
// the results show both counters the same
printf("%i\t%i\n", count1, count2);
SDL_DestroyRenderer(sdlRenderer);
SDL_DestroyWindow(sdlWindow);
SDL_Quit();
return 0;
}
// stutter test