FULLSCREEN_DESKTOP window causes stuttering

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

It looks like you’re running into timing precision problems. One reason you might only see it at “native” resolution is that at high resolution the small distances the object is moving are more apparent.

First, SDL_GetTicks() only gives millisecond accuracy, which isn’t enough for syncing to a display updating at 60hz (16.66666… milliseconds). Use SDL_GetPerformanceCounter() and SDL_GetPerformanceFrequency() for better accuracy.

Second, you have multiple places where you’re mixing floating point values with integers, and that isn’t helping.

Lastly, this is a… unique way of doing this. There really isn’t a good reason to run the CPU in a busy loop and then occasionally render a frame. If you want vsync, enable vsync and let that be what syncs your rendering to the display.

I’ve changed GetTicks for GetPerformanceCounter to increase accuracy and eliminate mixing doubles with integers. The result is the same.

Enabling VSYNC changes nothing.

I think you’ll find that a small movement is more noticeable on a lower resolution, not a higher one. Simply because the pixels are larger. And it doesn’t explain why the phenomena doesn’t happen when run in a window.

EDIT: The stuttering ONLY occurs when using either SDL_WINDOW_FULLSCREEN or SDL_WINDOW_FULLSCREEN_DESKTOP flags at native resolution.