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:
@@ -30,7 +30,8 @@ int main(int argc, char *argv[])
|
|||||||
video_cfg.render_height = SKELE_DEFAULT_RENDER_HEIGHT;
|
video_cfg.render_height = SKELE_DEFAULT_RENDER_HEIGHT;
|
||||||
video_cfg.window_width = 0;
|
video_cfg.window_width = 0;
|
||||||
video_cfg.window_height = 0;
|
video_cfg.window_height = 0;
|
||||||
video_cfg.flags = 0;
|
video_cfg.max_fps = 0;
|
||||||
|
video_cfg.flags = SKELE_VIDEO_VSYNC;
|
||||||
|
|
||||||
if (skele_video_init(video_cfg) != SKELE_INIT_SUCCESS) {
|
if (skele_video_init(video_cfg) != SKELE_INIT_SUCCESS) {
|
||||||
skele_stk_teardown();
|
skele_stk_teardown();
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ int main(int argc, char *argv[])
|
|||||||
video_cfg.render_height = SKELE_DEFAULT_RENDER_HEIGHT;
|
video_cfg.render_height = SKELE_DEFAULT_RENDER_HEIGHT;
|
||||||
video_cfg.window_width = 0;
|
video_cfg.window_width = 0;
|
||||||
video_cfg.window_height = 0;
|
video_cfg.window_height = 0;
|
||||||
video_cfg.flags = 0;
|
video_cfg.max_fps = 0;
|
||||||
|
video_cfg.flags = SKELE_VIDEO_VSYNC;
|
||||||
|
|
||||||
if (skele_video_init(video_cfg) != SKELE_INIT_SUCCESS) {
|
if (skele_video_init(video_cfg) != SKELE_INIT_SUCCESS) {
|
||||||
skele_stk_teardown();
|
skele_stk_teardown();
|
||||||
|
|||||||
@@ -12,10 +12,14 @@ extern "C" {
|
|||||||
#define SKELE_VIDEO_RESIZABLE 0x04
|
#define SKELE_VIDEO_RESIZABLE 0x04
|
||||||
#define SKELE_VIDEO_HIGHDPI 0x08
|
#define SKELE_VIDEO_HIGHDPI 0x08
|
||||||
#define SKELE_VIDEO_FULLSCREEN_EXCLUSIVE 0x10
|
#define SKELE_VIDEO_FULLSCREEN_EXCLUSIVE 0x10
|
||||||
|
#define SKELE_VIDEO_VSYNC 0x20
|
||||||
|
|
||||||
#define SKELE_DEFAULT_RENDER_WIDTH 320
|
#define SKELE_DEFAULT_RENDER_WIDTH 320
|
||||||
#define SKELE_DEFAULT_RENDER_HEIGHT 200
|
#define SKELE_DEFAULT_RENDER_HEIGHT 200
|
||||||
|
|
||||||
|
#define SKELE_VIDEO_FPS_UNCAPPED UINT16_MAX
|
||||||
|
#define SKELE_VIDEO_FPS_FALLBACK 60
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SKELE_FULLSCREEN_BORDERLESS,
|
SKELE_FULLSCREEN_BORDERLESS,
|
||||||
SKELE_FULLSCREEN_EXCLUSIVE
|
SKELE_FULLSCREEN_EXCLUSIVE
|
||||||
@@ -26,12 +30,16 @@ typedef struct {
|
|||||||
uint16_t render_height;
|
uint16_t render_height;
|
||||||
uint16_t window_width;
|
uint16_t window_width;
|
||||||
uint16_t window_height;
|
uint16_t window_height;
|
||||||
|
uint16_t max_fps;
|
||||||
uint8_t flags;
|
uint8_t flags;
|
||||||
} skele_video_config_t;
|
} skele_video_config_t;
|
||||||
|
|
||||||
uint8_t skele_video_init(skele_video_config_t cfg);
|
uint8_t skele_video_init(skele_video_config_t cfg);
|
||||||
void skele_video_shutdown(void);
|
void skele_video_shutdown(void);
|
||||||
void skele_video_present(void);
|
void skele_video_present(void);
|
||||||
|
uint8_t skele_video_using_vsync(void);
|
||||||
|
void skele_video_set_vsync(uint8_t enabled);
|
||||||
|
void skele_video_set_max_fps(uint16_t max_fps);
|
||||||
void skele_video_set_title(const char *title);
|
void skele_video_set_title(const char *title);
|
||||||
void skele_video_toggle_fullscreen(void);
|
void skele_video_toggle_fullscreen(void);
|
||||||
void skele_video_set_fullscreen_kind(skele_fullscreen_kind_t kind);
|
void skele_video_set_fullscreen_kind(skele_fullscreen_kind_t kind);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "client/video.h"
|
#include "client/video.h"
|
||||||
|
#include "clock.h"
|
||||||
#include "skele.h"
|
#include "skele.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <stk/stk_log.h>
|
#include <stk/stk_log.h>
|
||||||
@@ -12,6 +13,60 @@ static uint8_t is_fullscreen = 0;
|
|||||||
static skele_fullscreen_kind_t last_fullscreen_kind =
|
static skele_fullscreen_kind_t last_fullscreen_kind =
|
||||||
SKELE_FULLSCREEN_EXCLUSIVE;
|
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)
|
static uint8_t max_scale(uint16_t rw, uint16_t rh)
|
||||||
{
|
{
|
||||||
SDL_DisplayID display;
|
SDL_DisplayID display;
|
||||||
@@ -159,7 +214,10 @@ uint8_t skele_video_init(skele_video_config_t cfg)
|
|||||||
return SKELE_INIT_FAILURE;
|
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);
|
SDL_ShowWindow(window);
|
||||||
return SKELE_INIT_SUCCESS;
|
return SKELE_INIT_SUCCESS;
|
||||||
}
|
}
|
||||||
@@ -179,7 +237,20 @@ void skele_video_shutdown(void)
|
|||||||
is_fullscreen = 0;
|
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_cycle_scale(void) { cycle_scale(); }
|
||||||
|
|
||||||
void skele_video_toggle_fullscreen(void)
|
void skele_video_toggle_fullscreen(void)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "client/blit.h"
|
#include "client/blit.h"
|
||||||
#include "client/palette.h"
|
#include "client/palette.h"
|
||||||
#include "client/video.h"
|
#include "client/video.h"
|
||||||
|
#include "clock.h"
|
||||||
#include "skele.h"
|
#include "skele.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@@ -21,6 +22,60 @@ static uint8_t is_fullscreen = 0;
|
|||||||
static skele_fullscreen_kind_t last_fullscreen_kind =
|
static skele_fullscreen_kind_t last_fullscreen_kind =
|
||||||
SKELE_FULLSCREEN_EXCLUSIVE;
|
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_RenderPresent(renderer); }
|
||||||
|
|
||||||
|
static void present_capped(void)
|
||||||
|
{
|
||||||
|
uint64_t now, elapsed;
|
||||||
|
|
||||||
|
SDL_RenderPresent(renderer);
|
||||||
|
|
||||||
|
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_SetRenderVSync(renderer, 1))
|
||||||
|
vsync_active = 1;
|
||||||
|
else {
|
||||||
|
SDL_SetRenderVSync(renderer, 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)
|
static uint8_t max_scale(uint16_t rw, uint16_t rh)
|
||||||
{
|
{
|
||||||
SDL_DisplayID display;
|
SDL_DisplayID display;
|
||||||
@@ -181,7 +236,11 @@ uint8_t skele_video_init(skele_video_config_t cfg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST);
|
SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST);
|
||||||
SDL_SetRenderVSync(renderer, 1);
|
|
||||||
|
want_vsync = (cfg.flags & SKELE_VIDEO_VSYNC) ? 1 : 0;
|
||||||
|
configured_max_fps = cfg.max_fps;
|
||||||
|
apply_present_mode();
|
||||||
|
|
||||||
memset(palette, 0, sizeof(palette));
|
memset(palette, 0, sizeof(palette));
|
||||||
SDL_ShowWindow(window);
|
SDL_ShowWindow(window);
|
||||||
return SKELE_INIT_SUCCESS;
|
return SKELE_INIT_SUCCESS;
|
||||||
@@ -223,7 +282,20 @@ void skele_video_blit(uint8_t *pixels)
|
|||||||
SDL_RenderTexture(renderer, texture, NULL, NULL);
|
SDL_RenderTexture(renderer, texture, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void skele_video_present(void) { SDL_RenderPresent(renderer); }
|
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_cycle_scale(void) { cycle_scale(); }
|
||||||
|
|
||||||
void skele_video_toggle_fullscreen(void)
|
void skele_video_toggle_fullscreen(void)
|
||||||
|
|||||||
Reference in New Issue
Block a user