Hi all,
I have a game where I am allowing the player to enable/disable vsync, and I am just observing some behaviour with the frame times and am wondering if anyone could offer me some insight.
I am using the “main” function and while-loop API for SDL, and I am trying to run the game at a steady 60 FPS. When I have vsync turned on, the DT between each frame (“time_dt”) fluctuates times in the range of 16.58 - 17.14 ms.
If I add a wait function below the render, it stabilises it (see “DO_SLEEP”, I know there are functions like SDL_DelayPrecise but I’m just spinning the CPU for this example). Adding that line will keep DT values in a range very close to 16.666 ms.
My question is, are the fluctuations like this in frame times normal when vsync is turned on, or is there something stupid I am missing or doing wrong? Any help would be much appreciated!
Also one other question: When vsync is turned on, I get a bit of input lag for the events. Can this be removed also? Weirdly if i set the renderer to “opengl” it seems to get rid of the lag.
My current specs:
- Mac OS X (but I also get this behaviour on windows).
- SDL version 3.15
Here’s the example code:
/*
main.cpp
*/
#include <cstdlib>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
constexpr Uint64 NS_PER_FRAME = 16666666;
constexpr Uint64 LOG_EVERY_NS = 1000000000;
constexpr int VSYNC = 1;
constexpr bool DO_SLEEP = false;
/**
* SDL Entrypoint.
*/
int main(int argc, char *args[]) {
// For Mac OS X.
SDL_SetMainReady();
// Renderer.
//SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
// Init SDL.
if (!SDL_Init(SDL_INIT_VIDEO)) {
return EXIT_FAILURE;
}
// Set exit func.
atexit(SDL_Quit);
// Initialize the window.
SDL_Window *window;
SDL_Renderer *renderer;
if (!SDL_CreateWindowAndRenderer("My App", 500, 500, SDL_WINDOW_RESIZABLE, &window, &renderer)) {
SDL_Log("Could not create window. %s", SDL_GetError());
SDL_Quit();
return EXIT_FAILURE;
}
// Create 1x1 texture.
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING, 1, 1);
if (texture == nullptr) {
SDL_Log("Could not create texture. %s", SDL_GetError());
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return EXIT_FAILURE;
}
// Set texture to be red.
int pitch = 0;
uint8_t* ptr;
if (SDL_LockTexture(texture, nullptr, reinterpret_cast<void**>(&ptr), &pitch)) {
ptr[0] = 255;
ptr[1] = 0;
ptr[2] = 0;
SDL_UnlockTexture(texture);
}
// Log renderer properties.
const char* name = SDL_GetRendererName(renderer);
if (name != nullptr) {
SDL_Log("Renderer: %s", name);
}
// Set vsync value.
if (!SDL_SetRenderVSync(renderer, VSYNC)) {
SDL_Log("Could not set vsync: %s", SDL_GetError());
}
// Timer.
Uint64 last_time = SDL_GetTicksNS();
Uint64 log_at = 0;
// Render rect.
SDL_FRect texture_rect { 0.0f, 0.0f, 100.0f, 100.0f };
// Loop until the user quits.
bool quit = false;
while (!quit) {
// Get elapsed time.
const Uint64 curr_time = SDL_GetTicksNS();
Uint64 time_dt = curr_time - last_time;
last_time = curr_time;
// Log elapsed time every N nanoseconds.
log_at += time_dt;
if (log_at >= LOG_EVERY_NS) {
SDL_Log("DT: %llu", time_dt);
log_at = 0;
}
// Poll events.
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_EVENT_QUIT: {
quit = true;
break;
}
case SDL_EVENT_KEY_DOWN: {
if (event.key.repeat == 0) {
switch (event.key.key) {
case SDLK_UP: {
texture_rect.y -= 10.0f;
break;
}
case SDLK_DOWN: {
texture_rect.y += 10.0f;
break;
}
case SDLK_LEFT: {
texture_rect.x -= 10.0f;
break;
}
case SDLK_RIGHT: {
texture_rect.x += 10.0f;
break;
}
}
}
}
}
}
// Render.
SDL_SetRenderDrawColor(renderer, 128, 128, 128, 255);
SDL_RenderClear(renderer);
SDL_RenderTexture(renderer, texture, nullptr, &texture_rect);
SDL_RenderPresent(renderer);
// Sleep for remaining frame duration.
if (DO_SLEEP) {
const Uint64 next_frame_time = curr_time + NS_PER_FRAME;
while (SDL_GetTicksNS() < next_frame_time) {}
}
}
SDL_Log("Exiting");
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return EXIT_SUCCESS;
}