Skip to content

Graphical stuttering in Linux with SDL3 GPU #15069

@jaenis

Description

@jaenis

Hi!

I made small app using SDL3 GPU and noticed that it suffers from graphical stuttering.
Every now and then there is odd delay between draw calls. I made small example that reproduces this issue

I am using Xubuntu 24.04 with NVidia drivers 590

The test application creates Vulkan window and clears screen on every SDL_AppIterate call . Screen is cleared with a color that indicates how many milliseconds there were between draw calls. When running the application it is easy to see that window blinks red quite often (indicating long time between draw calls). In my case it blinks rougly twice per second.

App also logs the time between draw calls, it looks like this:

Elapsed: 17 ms   // Normal rendering
Elapsed: 17 ms
Elapsed: 67 ms   // Odd delay (stutter)
Elapsed: 0 ms    // ?
Elapsed: 0 ms
Elapsed: 0 ms
Elapsed: 16 ms   // Back to normal rendering
Elapsed: 17 ms
Elapsed: 17 ms

Here's the code that reproduces the issue:

#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_gpu.h>
#include <SDL3/SDL_timer.h>
#include <SDL3/SDL_log.h>

struct Context
{
    SDL_Window *window = nullptr;
    SDL_GPUDevice *device = nullptr;
    Uint64 lastTicks = 0;
};

SDL_AppResult SDL_AppInit(void **appstate, int /*argc*/, char ** /*argv*/)
{
    Context* context = new Context();
    *appstate = context;

    if (!SDL_Init(SDL_INIT_VIDEO))
        return SDL_APP_FAILURE;

    // Create Window
    SDL_WindowFlags flags = SDL_WINDOW_VULKAN;
    // flags |= SDL_WINDOW_FULLSCREEN;      // Issue happens in fullscreen also
    context->window = SDL_CreateWindow("Stutter test", 1920, 1080, flags);
    if (!context->window)
        return SDL_APP_FAILURE;

    // Create GPU Device
    context->device = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV, false, NULL);
    if (!context->device)
        return SDL_APP_FAILURE;

    // Claim window
    if (!SDL_ClaimWindowForGPUDevice(context->device, context->window))
        return SDL_APP_FAILURE;

    // Using immediate mode removes the stutter
    // SDL_SetGPUSwapchainParameters(context->device, context->window, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, SDL_GPU_PRESENTMODE_IMMEDIATE);

    context->lastTicks = SDL_GetTicks();
    return SDL_APP_CONTINUE;
}

void SDL_AppQuit(void *appstate, SDL_AppResult /*result*/)
{
    Context *context = reinterpret_cast<Context*>(appstate);
    if (context)
    {
        SDL_ReleaseWindowFromGPUDevice(context->device, context->window);
        SDL_DestroyGPUDevice(context->device);
        SDL_DestroyWindow(context->window);
        delete context;
    }

    SDL_Quit();
}

SDL_AppResult SDL_AppIterate(void *appstate)
{
    Context *context = reinterpret_cast<Context*>(appstate);

    // Calculate number of ticks between two iterations
    Uint64 now = SDL_GetTicks();
    Uint64 elapsed = now - context->lastTicks;
    context->lastTicks = now;
    SDL_Log("Elapsed: %ld ms", elapsed);

    // Acquire command buffer
    SDL_GPUCommandBuffer* commandBuffer = SDL_AcquireGPUCommandBuffer(context->device);

    // Get the swapchain texture
    uint32_t width, height;
    SDL_GPUTexture* swapchainTexture;
    SDL_WaitAndAcquireGPUSwapchainTexture(commandBuffer, context->window, &swapchainTexture, &width, &height);
    //SDL_AcquireGPUSwapchainTexture(commandBuffer, context->window, &swapchainTexture, &width, &height);
    if (swapchainTexture)
    {
        // Begin a render pass
        SDL_GPUColorTargetInfo colorTargetInfo{};
        colorTargetInfo.clear_color = {elapsed/100.0f,0,0, 1};  // Indicate elapsed time with red color
        colorTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR;
        colorTargetInfo.store_op = SDL_GPU_STOREOP_STORE;
        colorTargetInfo.texture = swapchainTexture;
        colorTargetInfo.cycle = true;
        SDL_GPURenderPass* renderPass = SDL_BeginGPURenderPass(commandBuffer, &colorTargetInfo, 1, NULL);

        // No draw commands, just clear the screen

        // End the render pass
        SDL_EndGPURenderPass(renderPass);
    }

    // Submit command buffer
    SDL_SubmitGPUCommandBuffer(commandBuffer);

    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent(void * /*appstate*/, SDL_Event *event)
{
    // Exit when window is closed
    if (event->type == SDL_EVENT_QUIT)
        return SDL_APP_SUCCESS;

    // Exit when ESC is pressed
    if (event->type == SDL_EVENT_KEY_DOWN && event->key.key == SDLK_ESCAPE)
        return SDL_APP_SUCCESS;

    return SDL_APP_CONTINUE;
}

Stuttering happens even when SDL_WaitAndAcquireGPUSwapchainTexture is changed to SDL_AcquireGPUSwapchainTexture.

Suttering stops if I set immediate mode after claiming window:
SDL_SetGPUSwapchainParameters(context->device, context->window, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, SDL_GPU_PRESENTMODE_IMMEDIATE);

I would not want to use immediate mode, waiting for VSync would be preferred.

Also, stuttering keeps happening even if I change Vulkan to OpenGL.

Any advice how to address this issue? Is it possible that this happens inside SDL3?

Environment:
SDL3 3.4.0
Xubuntu 24.04
NVidia drivers 590

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions