Failed to enable VSync on Intel(R) HD Graphics 630 (i7-7700)

I cannot enable it on Desktop:

    int status = SDL_GL_SetSwapInterval(1);
    if (status != 0)
    {
        SDL_Log("VSync failed: %s", SDL_GetError());
    }

Solution is to limit FPS:

SDL_AppResult SDL_AppIterate(void *appState)
{
    App *app = (App *)appState;

    // High-precision timing variables
    static Uint64 lastFrameTime = 0;
    const Uint64 targetFrameTimeNS = 16666666; // ~16.6ms for 60 FPS
    
    Uint64 now = SDL_GetTicksNS();
    
    // If we haven't hit the target time, yield the CPU
    if ((now - lastFrameTime) < targetFrameTimeNS)
    {
        // Sleep to yield control back to the Windows scheduler
        // This drops CPU usage from 15% to <1%
        SDL_Delay(1); 
        return SDL_APP_CONTINUE;
    }
    
    // Update our last frame time
    lastFrameTime = now;

    glClear(GL_COLOR_BUFFER_BIT);

Before:

After:

P.S. I use the latest Intel drivers:

I think a browser does not need a VSync but it is failed to enable VSync on the browser too. The browser app spends 1-2% of CPU. I will not limit FPS for the browser because requestAnimationFrame works perfect. The browser limits FPS to 60 and plays animations very smoothly. This problem is actual for Dektop.

This is my example in for Desktop and prints VSync failed but I solved the problem (with 15% CPU) using the limit of FPS from the first message for the topic. Full code:

main.c

#define SDL_MAIN_USE_CALLBACKS 1

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <glad/glad.h>

typedef struct
{
    SDL_Window *window;
    SDL_GLContext glContext;
    GLuint shaderProgram;
    GLuint vao, vbo;
} App;

const char *vertexShaderSource =
    "#version 330 core\n"
    "layout (location = 0) in vec3 aPosition;\n"
    "void main()\n"
    "{\n"
    "    gl_Position = vec4(aPosition, 1.0);\n"
    "}\n";

const char *fragmentShaderSource =
    "#version 330 core\n"
    "precision mediump float;\n"
    "out vec4 fragColor;\n"
    "void main()\n"
    "{\n"
    "    fragColor = vec4(0.75, 1, 0.7, 1.0);\n"
    "}\n";

SDL_AppResult SDL_AppInit(void **appState, int argc, char *argv[])
{
    App *app = (App *)SDL_malloc(sizeof(App));
    *appState = app;

    if (!SDL_Init(SDL_INIT_VIDEO))
    {
        SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); // Enable MULTISAMPLE
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8); // Can be 2, 4, 8 or 16

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
        SDL_GL_CONTEXT_PROFILE_CORE);

    app->window = SDL_CreateWindow("SDL3, OpenGL", 380, 380, SDL_WINDOW_OPENGL);
    if (!app->window)
    {
        SDL_Log("Couldn't create the window: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    app->glContext = SDL_GL_CreateContext(app->window);
    if (!app->glContext)
    {
        SDL_Log("Couldn't create the glConext: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
    {
        SDL_Log("Failed to initialize OpenGL function pointers");
        return SDL_APP_FAILURE;
    }

    int status = SDL_GL_SetSwapInterval(1);
    if (status != 0)
    {
        SDL_Log("VSync failed: %s", SDL_GetError());
    }

    glClearColor(0.2f, 0.2f, 0.2f, 1.f);

    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);

    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);

    app->shaderProgram = glCreateProgram();
    glAttachShader(app->shaderProgram, vertexShader);
    glAttachShader(app->shaderProgram, fragmentShader);
    glLinkProgram(app->shaderProgram);
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    glUseProgram(app->shaderProgram);

    // clang-format off
    GLfloat vertexPositions[] = {
        -0.5f, -0.5f, 0.f,
        0.5f, -0.5f, 0.f,
        0.f, 0.5f, 0.f
    };
    // clang-format on

    glGenVertexArrays(1, &app->vao);
    glGenBuffers(1, &app->vbo);
    glBindVertexArray(app->vao);
    glBindBuffer(GL_ARRAY_BUFFER, app->vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (void *)0);
    glEnableVertexAttribArray(0);
    glBindVertexArray(0);

    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent(void *appState, SDL_Event *event)
{
    App *app = (App *)appState;

    switch (event->type)
    {
        case SDL_EVENT_QUIT:
            return SDL_APP_SUCCESS;
        default:
            break;
    }

    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppIterate(void *appState)
{
    App *app = (App *)appState;

    // High-precision timing variables
    static Uint64 lastFrameTime = 0;
    const Uint64 targetFrameTimeNS = 16666666; // ~16.6ms for 60 FPS

    Uint64 now = SDL_GetTicksNS();

    // If we haven't hit the target time, yield the CPU
    if ((now - lastFrameTime) < targetFrameTimeNS)
    {
        // Sleep to yield control back to the Windows scheduler
        // This drops CPU usage from 15% to <1%
        SDL_Delay(1);
        return SDL_APP_CONTINUE;
    }

    // Update our last frame time
    lastFrameTime = now;

    glClear(GL_COLOR_BUFFER_BIT);

    glBindVertexArray(app->vao);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    glBindVertexArray(0);
    SDL_GL_SwapWindow(app->window);
    return SDL_APP_CONTINUE;
}

void SDL_AppQuit(void *appState, SDL_AppResult result)
{
    App *app = (App *)appState;
    glDeleteProgram(app->shaderProgram);
    SDL_GL_DestroyContext(app->glContext);
    SDL_DestroyWindow(app->window);
    SDL_Quit();
}

Since you’re using the main-callbacks functionality in SDL3, you might want to consider setting the callback rate of SDL_AppIterate, instead of doing manual frame limiting.

Code example:

SDL_AppResult SDL_AppInit(void** appState, int argc, char* argv)
{
	if(!SDL_Init(SDL_INIT_VIDEO))
		return SDL_AppResult::SDL_APP_FAILURE;

	SDL_Window*		window		= nullptr;
	SDL_Renderer*	renderer	= nullptr;
	if(!SDL_CreateWindowAndRenderer("Window", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_HIDDEN, &window, &renderer))
		return SDL_AppResult::SDL_APP_FAILURE;

	/**
	* "0" = SDL_AppIterate will be called as often as possible
	* "30" = SDL_AppIterate will be called 30 times per seconds (30 FPS)
	* "60" = SDL_AppIterate will be called 60 times per seconds (60 FPS)
	* "120" = SDL_AppIterate will be called 120 times per seconds (120 FPS)
	* Other integer values, in quotes
	* "59.94" = SDL doesn't round values, so 59.94 will be respected and used 
	* "waitevent" = SDL_AppIterate will not be called until new event(s) have arrived (and been processed by SDL_AppEvent)
	*/
	if(!SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "0"))
		return SDL_AppResult::SDL_APP_FAILURE;

	return SDL_APP_CONTINUE;

}
1 Like

I don’t know why vsync does not work for you but its purpose is not only to avoid wasting CPU power but also to avoid tearing.

2 Likes
int status = SDL_GL_SetSwapInterval(1);

    if (status != 0)
    {
        SDL_Log("VSync failed: %s", SDL_GetError());
    }

In C/C++ bool values false == 0
The function reports true on success.
So I’m seeing if(status != false) above, which means you are reporting a failure upon success.
Edit: this happened to me enough times that I only use the keywords true/false instead of zero and not-zero when possible.

1 Like

Thanks! I fixed it:

    const int VSYNC_OFF = 0;
    const int VSYNC_ON = 1;
    if (!SDL_GL_SetSwapInterval(VSYNC_ON))
    {
        SDL_Log("VSync failed: %s", SDL_GetError());
    }

It no longer reports a ‘VSync failed’ error in the console. My monitor (Dell E2420HS) supports a maximum of 60 FPS, so I assume VSync is effectively capping the framerate. However, my app still consumes about 15% CPU. Is this expected behavior?

Thanks! It works!

It works for a simple triangle from the first post:

And for this simplified version (a setting of a background only):

main.c

#define SDL_MAIN_USE_CALLBACKS 1

#include <glad/glad.h>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>

typedef struct
{
    SDL_Window *window;
    SDL_GLContext glContext;
} App;

SDL_AppResult SDL_AppInit(void **appState, int argc, char *argv[])
{
    App *app = (App *)SDL_malloc(sizeof(App));
    *appState = app;

    if(!SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "60"))
    {
        SDL_Log("Failed to set a frame rate: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    if (!SDL_Init(SDL_INIT_VIDEO))
    {
        SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); // Enable MULTISAMPLE
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8); // Can be 2, 4, 8 or 16

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

    app->window = SDL_CreateWindow("SDL3, OpenGL", 380, 380, SDL_WINDOW_OPENGL);
    if (!app->window)
    {
        SDL_Log("Couldn't create the window: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    app->glContext = SDL_GL_CreateContext(app->window);
    if (!app->glContext)
    {
        SDL_Log("Couldn't create the glConext: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
    {
        SDL_Log("Failed to initialize OpenGL function pointers");
        return SDL_APP_FAILURE;
    }

    glClearColor(0.2f, 0.2f, 0.2f, 1.f);

    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent(void *appState, SDL_Event *event)
{
    switch (event->type)
    {
        case SDL_EVENT_QUIT:
            return SDL_APP_SUCCESS;
        default:
            break;
    }

    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppIterate(void *appState)
{
    App *app = (App *)appState;

    glClear(GL_COLOR_BUFFER_BIT);

    SDL_GL_SwapWindow(app->window);
    return SDL_APP_CONTINUE;
}

void SDL_AppQuit(void *appState, SDL_AppResult result)
{
    App *app = (App *)appState;
    SDL_GL_DestroyContext(app->glContext);
    SDL_DestroyWindow(app->window);
    SDL_Quit();
}

SDL_GL_SetSwapInterval used to returned an int (0 on success, -1 on failure) in SDL2 so that piece of code is correct for SDL2 but not for SDL3.


Does your CPU have six cores by any chance?

100% / 6 ≈ 16.7 %

4 cores, 8 virtual threads:

I’m not sure but I suspect vsync is not working for you. I suggest you count and print the number of times SDL_AppIterate is called each second to find out.

1 Like

This is exactly a minimum program that I ran where I enagle VSync and SDL_GL_SetSwapInterval returns true:

main.c

#define SDL_MAIN_USE_CALLBACKS 1

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <glad/glad.h>

typedef struct
{
    SDL_Window *window;
    SDL_GLContext glContext;
} App;

SDL_AppResult SDL_AppInit(void **appState, int argc, char *argv[])
{
    SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
    SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");

    App *app = (App *)SDL_malloc(sizeof(App));
    *appState = app;

    if (!SDL_Init(SDL_INIT_VIDEO))
    {
        SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); // Enable MULTISAMPLE
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8); // Can be 2, 4, 8 or 16

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

    app->window = SDL_CreateWindow("SDL3, OpenGL", 380, 380, SDL_WINDOW_OPENGL);
    if (!app->window)
    {
        SDL_Log("Couldn't create the window: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    app->glContext = SDL_GL_CreateContext(app->window);
    if (!app->glContext)
    {
        SDL_Log("Couldn't create the glConext: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    if (!SDL_GL_MakeCurrent(app->window, app->glContext))
    {
        SDL_Log("Couldn't make context current: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
    {
        SDL_Log("Failed to initialize OpenGL function pointers");
        return SDL_APP_FAILURE;
    }

    const unsigned char *renderer = glGetString(GL_RENDERER);
    const unsigned char *vendor = glGetString(GL_VENDOR);
    SDL_Log("Vendor: %s", vendor);
    SDL_Log("Renderer: %s", renderer);

    SDL_Log("SDL Compiled Version: %d.%d.%d",
        SDL_MAJOR_VERSION,
        SDL_MINOR_VERSION,
        SDL_MICRO_VERSION);

    int linked_version = SDL_GetVersion();
    SDL_Log("SDL Linked Version (raw): %d", linked_version);

    SDL_Log("SDL Linked Version: %d.%d.%d",
        SDL_VERSIONNUM_MAJOR(linked_version),
        SDL_VERSIONNUM_MINOR(linked_version),
        SDL_VERSIONNUM_MICRO(linked_version));

    glClearColor(0.2f, 0.2f, 0.2f, 1.f);

    SDL_ShowWindow(app->window);

    const int VSYNC_OFF = 0;
    const int VSYNC_ON = 1;
    if (!SDL_GL_SetSwapInterval(VSYNC_ON))
    {
        SDL_Log("VSync failed: %s", SDL_GetError());
    }

    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent(void *appState, SDL_Event *event)
{
    App *app = (App *)appState;

    switch (event->type)
    {
        case SDL_EVENT_QUIT:
            return SDL_APP_SUCCESS;
        default:
            break;
    }

    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppIterate(void *appState)
{
    App *app = (App *)appState;

    glClear(GL_COLOR_BUFFER_BIT);

    SDL_GL_SwapWindow(app->window);
    return SDL_APP_CONTINUE;
}

void SDL_AppQuit(void *appState, SDL_AppResult result)
{
    App *app = (App *)appState;
    SDL_GL_DestroyContext(app->glContext);
    SDL_DestroyWindow(app->window);
    SDL_Quit();
}

Project files: vsync-failed-min-app-opengl-sdl3-mingw-c.zip (3.0 KB)

Thank you! I will do it.

Test with FPS and VSync:

main.c

#define SDL_MAIN_USE_CALLBACKS 1

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <glad/glad.h>

typedef struct
{
    SDL_Window *window;
    SDL_GLContext glContext;
    Uint64 frameCount;
    Uint64 lastLogTime;
} App;

SDL_AppResult SDL_AppInit(void **appState, int argc, char *argv[])
{
    SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
    SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");

    App *app = (App *)SDL_malloc(sizeof(App));
    *appState = app;

    app->frameCount = 0;
    app->lastLogTime = SDL_GetTicks();

    if (!SDL_Init(SDL_INIT_VIDEO))
    {
        SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); // Enable MULTISAMPLE
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8); // Can be 2, 4, 8 or 16

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

    app->window = SDL_CreateWindow("SDL3, OpenGL", 380, 380, SDL_WINDOW_OPENGL);
    if (!app->window)
    {
        SDL_Log("Couldn't create the window: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    app->glContext = SDL_GL_CreateContext(app->window);
    if (!app->glContext)
    {
        SDL_Log("Couldn't create the glConext: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    if (!SDL_GL_MakeCurrent(app->window, app->glContext))
    {
        SDL_Log("Couldn't make context current: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
    {
        SDL_Log("Failed to initialize OpenGL function pointers");
        return SDL_APP_FAILURE;
    }

    const unsigned char *renderer = glGetString(GL_RENDERER);
    const unsigned char *vendor = glGetString(GL_VENDOR);
    SDL_Log("Vendor: %s", vendor);
    SDL_Log("Renderer: %s", renderer);

    SDL_Log("SDL Compiled Version: %d.%d.%d",
        SDL_MAJOR_VERSION,
        SDL_MINOR_VERSION,
        SDL_MICRO_VERSION);

    int linked_version = SDL_GetVersion();
    SDL_Log("SDL Linked Version (raw): %d", linked_version);

    SDL_Log("SDL Linked Version: %d.%d.%d",
        SDL_VERSIONNUM_MAJOR(linked_version),
        SDL_VERSIONNUM_MINOR(linked_version),
        SDL_VERSIONNUM_MICRO(linked_version));

    glClearColor(0.2f, 0.2f, 0.2f, 1.f);

    SDL_ShowWindow(app->window);

    const int VSYNC_OFF = 0;
    const int VSYNC_ON = 1;
    if (!SDL_GL_SetSwapInterval(VSYNC_ON))
    {
        SDL_Log("VSync failed: %s", SDL_GetError());
    }

    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent(void *appState, SDL_Event *event)
{
    App *app = (App *)appState;

    switch (event->type)
    {
        case SDL_EVENT_QUIT:
            return SDL_APP_SUCCESS;
        default:
            break;
    }

    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppIterate(void *appState)
{
    App *app = (App *)appState;

    app->frameCount++;
    Uint64 now = SDL_GetTicks();
    if (now - app->lastLogTime >= 1000)
    {
        SDL_Log("Frames per second: %llu", app->frameCount);
        app->frameCount = 0;
        app->lastLogTime = now;
    }

    glClear(GL_COLOR_BUFFER_BIT);

    SDL_GL_SwapWindow(app->window);
    return SDL_APP_CONTINUE;
}

void SDL_AppQuit(void *appState, SDL_AppResult result)
{
    App *app = (App *)appState;
    SDL_GL_DestroyContext(app->glContext);
    SDL_DestroyWindow(app->window);
    SDL_Quit();
}

I try to enable VSync in two places. Here:

    SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");

and here:

    const int VSYNC_OFF = 0;
    const int VSYNC_ON = 1;
    if (!SDL_GL_SetSwapInterval(VSYNC_ON))
    {
        SDL_Log("VSync failed: %s", SDL_GetError());
    }

So, it looks like VSync was enabled because SDL_GL_SetSwapInterval returns true and I see 60 FPS. Without VSync I see this:

main.c

#define SDL_MAIN_USE_CALLBACKS 1

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <glad/glad.h>

typedef struct
{
    SDL_Window *window;
    SDL_GLContext glContext;
    Uint64 frameCount;
    Uint64 lastLogTime;
} App;

SDL_AppResult SDL_AppInit(void **appState, int argc, char *argv[])
{
    SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
    // SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");

    App *app = (App *)SDL_malloc(sizeof(App));
    *appState = app;

    app->frameCount = 0;
    app->lastLogTime = SDL_GetTicks();

    if (!SDL_Init(SDL_INIT_VIDEO))
    {
        SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); // Enable MULTISAMPLE
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8); // Can be 2, 4, 8 or 16

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

    app->window = SDL_CreateWindow("SDL3, OpenGL", 380, 380, SDL_WINDOW_OPENGL);
    if (!app->window)
    {
        SDL_Log("Couldn't create the window: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    app->glContext = SDL_GL_CreateContext(app->window);
    if (!app->glContext)
    {
        SDL_Log("Couldn't create the glConext: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    if (!SDL_GL_MakeCurrent(app->window, app->glContext))
    {
        SDL_Log("Couldn't make context current: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
    {
        SDL_Log("Failed to initialize OpenGL function pointers");
        return SDL_APP_FAILURE;
    }

    const unsigned char *renderer = glGetString(GL_RENDERER);
    const unsigned char *vendor = glGetString(GL_VENDOR);
    SDL_Log("Vendor: %s", vendor);
    SDL_Log("Renderer: %s", renderer);

    SDL_Log("SDL Compiled Version: %d.%d.%d",
        SDL_MAJOR_VERSION,
        SDL_MINOR_VERSION,
        SDL_MICRO_VERSION);

    int linked_version = SDL_GetVersion();
    SDL_Log("SDL Linked Version (raw): %d", linked_version);

    SDL_Log("SDL Linked Version: %d.%d.%d",
        SDL_VERSIONNUM_MAJOR(linked_version),
        SDL_VERSIONNUM_MINOR(linked_version),
        SDL_VERSIONNUM_MICRO(linked_version));

    glClearColor(0.2f, 0.2f, 0.2f, 1.f);

    SDL_ShowWindow(app->window);

    // const int VSYNC_OFF = 0;
    // const int VSYNC_ON = 1;
    // if (!SDL_GL_SetSwapInterval(VSYNC_ON))
    // {
    //     SDL_Log("VSync failed: %s", SDL_GetError());
    // }

    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent(void *appState, SDL_Event *event)
{
    App *app = (App *)appState;

    switch (event->type)
    {
        case SDL_EVENT_QUIT:
            return SDL_APP_SUCCESS;
        default:
            break;
    }

    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppIterate(void *appState)
{
    App *app = (App *)appState;

    app->frameCount++;
    Uint64 now = SDL_GetTicks();
    if (now - app->lastLogTime >= 1000)
    {
        SDL_Log("Frames per second: %llu", app->frameCount);
        app->frameCount = 0;
        app->lastLogTime = now;
    }

    glClear(GL_COLOR_BUFFER_BIT);

    SDL_GL_SwapWindow(app->window);
    return SDL_APP_CONTINUE;
}

void SDL_AppQuit(void *appState, SDL_AppResult result)
{
    App *app = (App *)appState;
    SDL_GL_DestroyContext(app->glContext);
    SDL_DestroyWindow(app->window);
    SDL_Quit();
}

VSync caps the FPS at 60 and limits GPU usage, but it does not limit CPU usage. Should it?

Yes, I would have expected vsync to lower the CPU usage. Not sure why it doesn’t. Maybe the driver implements it as “busy wait”, I really have no idea…

1 Like

VSync works on my computer for SDL_Renderer:

main.c

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>

int main(int argc, char *argv[])
{
    if (!SDL_Init(SDL_INIT_VIDEO))
    {
        return -1;
    }

    SDL_Window *window = SDL_CreateWindow("SDL3, C", 380, 380, 0);
    SDL_Renderer *renderer = SDL_CreateRenderer(window, NULL);

    // Enable VSync (1 = sync with every refresh)
    if (!SDL_SetRenderVSync(renderer, 1))
    {
        SDL_Log("Failed to enable VSync: %s", SDL_GetError());
    }

    bool running = true;
    SDL_Event event;

    while (running)
    {
        while (SDL_PollEvent(&event))
        {
            if (event.type == SDL_EVENT_QUIT)
            {
                running = false;
            }
        }

        SDL_SetRenderDrawColor(renderer, 20, 20, 20, 255);
        SDL_RenderClear(renderer);

        // Rectangle 1 (Red)
        SDL_FRect rect1 = { 50.0f, 150.0f, 100.0f, 50.0f };
        SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
        SDL_RenderFillRect(renderer, &rect1);

        // Rectangle 2 (Green)
        SDL_FRect rect2 = { 200.0f, 150.0f, 100.0f, 50.0f };
        SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
        SDL_RenderFillRect(renderer, &rect2);

        // Rectangle 3 (Blue)
        SDL_FRect rect3 = { 125.0f, 50.0f, 100.0f, 50.0f };
        SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
        SDL_RenderFillRect(renderer, &rect3);

        SDL_RenderPresent(renderer);
    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

VSync works for SDL3-callbacks too for SDL_Renderer:

main.c

#define SDL_MAIN_USE_CALLBACKS 1 // Use callback functions instead of main()

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>

// We will use this renderer to draw into this window every frame
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;

// This function runs once at startup
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
    if (!SDL_Init(SDL_INIT_VIDEO))
    {
        SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    if (!SDL_CreateWindowAndRenderer("SDL3 Rectangles", 380, 380, 0, &window, &renderer))
    {
        SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    // Enable VSync (1 = sync with every refresh)
    if (!SDL_SetRenderVSync(renderer, 1))
    {
        SDL_Log("Failed to enable VSync: %s", SDL_GetError());
    }

    return SDL_APP_CONTINUE; // Continue program execution!
}

// This function runs whenever a new event occurs (mouse input, key presses, etc.)
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
    if (event->type == SDL_EVENT_QUIT)
    {
        return SDL_APP_SUCCESS; // Terminate the program, reporting success to the OS
    }
    return SDL_APP_CONTINUE; // Continue program execution!
}

// This function runs once per frame and is the heart of the program
SDL_AppResult SDL_AppIterate(void *appstate)
{
    SDL_SetRenderDrawColor(renderer, 38, 43, 51, 255);

    // Clear the window with the draw color
    SDL_RenderClear(renderer);

    // Rectangle 1 (Red)
    SDL_FRect rect1 = { 50.0f, 150.0f, 100.0f, 50.0f };
    SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
    SDL_RenderFillRect(renderer, &rect1);

    // Rectangle 2 (Green)
    SDL_FRect rect2 = { 200.0f, 150.0f, 100.0f, 50.0f };
    SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
    SDL_RenderFillRect(renderer, &rect2);

    // Rectangle 3 (Blue)
    SDL_FRect rect3 = { 125.0f, 50.0f, 100.0f, 50.0f };
    SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
    SDL_RenderFillRect(renderer, &rect3);

    // Present the freshly cleared result to the screen
    SDL_RenderPresent(renderer);

    return SDL_APP_CONTINUE; // Continue program execution!
}

// This function runs once upon shutdown
void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
    // SDL will clean up the window and renderer for us
}

CMakeLists.txt

set(CMAKE_BUILD_TYPE "Debug")
cmake_minimum_required(VERSION 3.21)
project(rectangles-callbacks-sdl3-mingw-c)

set(CMAKE_C_STANDARD 17)
set(CMAKE_C_STANDARD_REQUIRED ON)

set(SDL3_DIR "C:/libs/SDL3-devel-3.4.12-mingw/lib/cmake/SDL3")
find_package(SDL3 REQUIRED)

add_executable(app)

target_sources(app
PRIVATE
    src/main.c
)

target_link_libraries(app PRIVATE SDL3::SDL3)

if (CMAKE_BUILD_TYPE STREQUAL "Debug")
    target_link_options(app PRIVATE -mconsole)
else()
    target_link_options(app PRIVATE -mwindows)
endif()

I don’t know much about openGL, but I found this:
Try calling glFinish after window swap. That seems to sync the CPU with the GPU refresh rate.

  • The function pauses until all previous GL functions have finished their action, and since you set the swap interval, it pauses the CPU for a full vsync.

The bottom of that link says glFinish is not recommended for games with high-action and high-throughput needs, but good for smaller apps.

It looks like glFinish (Kronos doc) is basically available for all modern versions of OpenGL.

You likely know this, but I should say that VSync should not directly control your game’s physics. If your character moves 4 pixels per frame, then proper gameplay is directly locked to your FPS. Many gaming monitors now run anywhere from 144 to 1000 FPS (and beyond). If you code expecting 60FPS, then those high FPS devices might run your game from 2 to 16 times faster than expected.
I still use VSync wrong in test code for simplicity’s sake, but for production code I think delta-time is the preferred method: Hints to Fixing Game Timing.

2 Likes