feat: add vsync-priority frame pacing to video backends
Present now dispatches through a function pointer chosen at init and re-chosen by two new runtime setters: real vsync if requested and the driver grants it, else a manual sleep-based cap (auto-detects display refresh rate, falls back to 60fps if that can't be queried), else fully uncapped. skele_video_using_vsync() reports what was actually granted; skele_video_set_vsync()/skele_video_set_max_fps() let a game change either live without tearing down the renderer/GL context.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#include "client/video.h"
|
||||
#include "clock.h"
|
||||
#include "skele.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stk/stk_log.h>
|
||||
@@ -12,6 +13,60 @@ static uint8_t is_fullscreen = 0;
|
||||
static skele_fullscreen_kind_t last_fullscreen_kind =
|
||||
SKELE_FULLSCREEN_EXCLUSIVE;
|
||||
|
||||
static void (*present_fn)(void);
|
||||
static uint64_t frame_budget_ns = 0;
|
||||
static uint64_t last_present_ns = 0;
|
||||
static uint8_t vsync_active = 0;
|
||||
static uint8_t want_vsync = 0;
|
||||
static uint16_t configured_max_fps = 0;
|
||||
|
||||
static void present(void) { SDL_GL_SwapWindow(window); }
|
||||
|
||||
static void present_capped(void)
|
||||
{
|
||||
uint64_t now, elapsed;
|
||||
|
||||
SDL_GL_SwapWindow(window);
|
||||
|
||||
now = skele_time_ns();
|
||||
elapsed = now - last_present_ns;
|
||||
if (elapsed < frame_budget_ns)
|
||||
skele_sleep_ns(frame_budget_ns - elapsed);
|
||||
last_present_ns = skele_time_ns();
|
||||
}
|
||||
|
||||
static void apply_present_mode(void)
|
||||
{
|
||||
const SDL_DisplayMode *refresh_mode;
|
||||
uint16_t cap;
|
||||
|
||||
if (want_vsync && SDL_GL_SetSwapInterval(1))
|
||||
vsync_active = 1;
|
||||
else {
|
||||
SDL_GL_SetSwapInterval(0);
|
||||
vsync_active = 0;
|
||||
}
|
||||
|
||||
cap = configured_max_fps;
|
||||
|
||||
if (cap == SKELE_VIDEO_FPS_UNCAPPED || (vsync_active && !cap)) {
|
||||
present_fn = present;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cap) {
|
||||
refresh_mode = SDL_GetCurrentDisplayMode(
|
||||
SDL_GetDisplayForWindow(window));
|
||||
cap = (refresh_mode && refresh_mode->refresh_rate > 0)
|
||||
? (uint16_t)refresh_mode->refresh_rate
|
||||
: SKELE_VIDEO_FPS_FALLBACK;
|
||||
}
|
||||
|
||||
frame_budget_ns = 1000000000ULL / cap;
|
||||
last_present_ns = skele_time_ns();
|
||||
present_fn = present_capped;
|
||||
}
|
||||
|
||||
static uint8_t max_scale(uint16_t rw, uint16_t rh)
|
||||
{
|
||||
SDL_DisplayID display;
|
||||
@@ -159,7 +214,10 @@ uint8_t skele_video_init(skele_video_config_t cfg)
|
||||
return SKELE_INIT_FAILURE;
|
||||
}
|
||||
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
want_vsync = (cfg.flags & SKELE_VIDEO_VSYNC) ? 1 : 0;
|
||||
configured_max_fps = cfg.max_fps;
|
||||
apply_present_mode();
|
||||
|
||||
SDL_ShowWindow(window);
|
||||
return SKELE_INIT_SUCCESS;
|
||||
}
|
||||
@@ -179,7 +237,20 @@ void skele_video_shutdown(void)
|
||||
is_fullscreen = 0;
|
||||
}
|
||||
|
||||
void skele_video_present(void) { SDL_GL_SwapWindow(window); }
|
||||
void skele_video_present(void) { present_fn(); }
|
||||
uint8_t skele_video_using_vsync(void) { return vsync_active; }
|
||||
|
||||
void skele_video_set_vsync(uint8_t enabled)
|
||||
{
|
||||
want_vsync = enabled;
|
||||
apply_present_mode();
|
||||
}
|
||||
|
||||
void skele_video_set_max_fps(uint16_t max_fps)
|
||||
{
|
||||
configured_max_fps = max_fps;
|
||||
apply_present_mode();
|
||||
}
|
||||
void skele_video_cycle_scale(void) { cycle_scale(); }
|
||||
|
||||
void skele_video_toggle_fullscreen(void)
|
||||
|
||||
Reference in New Issue
Block a user