forked from Ports/perfect_dark
Also implements a new menu item flag: MENUITEMFLAG_SLIDER_DEFERRED, which only calls the MENUOP_GETSLIDER callback on entering dimmed mode, storing its value for later ticks, and only calls the MENUOP_SET callback on leaving dimmed mode. This was necessary to prevent the framerate limit slider from immediately affecting the framerate, leading to an annoying slow down as the value was decremented to 0.
459 lines
15 KiB
C++
459 lines
15 KiB
C++
#include <stdio.h>
|
|
#include <SDL.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
|
|
#include "platform.h"
|
|
#include "system.h"
|
|
|
|
#include "gfx_window_manager_api.h"
|
|
#include "gfx_screen_config.h"
|
|
|
|
static SDL_Window* wnd;
|
|
static SDL_GLContext ctx;
|
|
static SDL_Renderer* renderer;
|
|
static int sdl_to_lus_table[512];
|
|
static bool vsync_enabled = true;
|
|
// OTRTODO: These are redundant. Info can be queried from SDL.
|
|
static int window_width = DESIRED_SCREEN_WIDTH;
|
|
static int window_height = DESIRED_SCREEN_HEIGHT;
|
|
static uint32_t fullscreen_flag = SDL_WINDOW_FULLSCREEN_DESKTOP;
|
|
static bool fullscreen_state;
|
|
static bool maximized_state;
|
|
static bool is_running = true;
|
|
static void (*on_fullscreen_changed_callback)(bool is_now_fullscreen);
|
|
|
|
static int target_fps = 120; // above 60 since vsync is enabled by default
|
|
static uint64_t previous_time;
|
|
static uint64_t qpc_freq;
|
|
|
|
#define FRAME_INTERVAL_US_NUMERATOR 1000000
|
|
#define FRAME_INTERVAL_US_DENOMINATOR (target_fps)
|
|
|
|
static int32_t gfx_sdl_get_maximized_state(void) {
|
|
return (int32_t)maximized_state;
|
|
}
|
|
|
|
static int32_t gfx_sdl_get_fullscreen_state(void) {
|
|
return (int32_t)fullscreen_state;
|
|
}
|
|
|
|
static int32_t gfx_sdl_get_fullscreen_flag_mode(void) {
|
|
return fullscreen_flag == SDL_WINDOW_FULLSCREEN_DESKTOP ? 0 : 1;
|
|
}
|
|
|
|
static void gfx_sdl_set_fullscreen_flag(int32_t mode) {
|
|
switch (mode) {
|
|
case 0: {
|
|
fullscreen_flag = SDL_WINDOW_FULLSCREEN_DESKTOP;
|
|
} break;
|
|
case 1: {
|
|
fullscreen_flag = SDL_WINDOW_FULLSCREEN;
|
|
} break;
|
|
}
|
|
}
|
|
|
|
static void set_fullscreen(bool on, bool call_callback) {
|
|
fullscreen_state = on;
|
|
SDL_SetWindowFullscreen(wnd, on ? fullscreen_flag : 0);
|
|
if (call_callback && on_fullscreen_changed_callback) {
|
|
on_fullscreen_changed_callback(on);
|
|
}
|
|
}
|
|
|
|
static void set_maximize_window(bool on) {
|
|
maximized_state = on;
|
|
if (on) {
|
|
SDL_MaximizeWindow(wnd);
|
|
} else {
|
|
SDL_RestoreWindow (wnd);
|
|
}
|
|
}
|
|
|
|
static void gfx_sdl_get_active_window_refresh_rate(uint32_t* refresh_rate) {
|
|
int display_in_use = SDL_GetWindowDisplayIndex(wnd);
|
|
|
|
SDL_DisplayMode mode;
|
|
SDL_GetCurrentDisplayMode(display_in_use, &mode);
|
|
*refresh_rate = mode.refresh_rate;
|
|
}
|
|
|
|
static void gfx_sdl_init(const struct GfxWindowInitSettings *set) {
|
|
window_width = set->width;
|
|
window_height = set->height;
|
|
|
|
#ifdef SDL_HINT_VIDEO_HIGHDPI_DISABLED
|
|
if (!set->allow_hidpi) {
|
|
// HiDPI control, if available
|
|
SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "1");
|
|
#if defined(PLATFORM_WIN32) && defined(SDL_HINT_WINDOWS_DPI_AWARENESS)
|
|
// if HiDPI is disabled, declare ourselves DPI aware to get 1:1 window size on Windows
|
|
SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitor");
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
|
|
sysFatalError("Could not init SDL:\n%s", SDL_GetError());
|
|
}
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
|
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
|
|
if (sysArgCheck("--debug-gl")) {
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
|
|
}
|
|
|
|
int posX = set->x;
|
|
int posY = set->y;
|
|
int display_in_use = SDL_GetWindowDisplayIndex(wnd);
|
|
if (display_in_use < 0) { // Fallback to default if out of bounds
|
|
posX = 100;
|
|
posY = 100;
|
|
}
|
|
|
|
if (set->centered) {
|
|
SDL_DisplayMode mode = {};
|
|
SDL_GetCurrentDisplayMode(0, &mode);
|
|
posX = mode.w / 2 - window_width / 2;
|
|
posY = mode.h / 2 - window_height / 2;
|
|
}
|
|
|
|
if (set->fullscreen_is_exclusive) {
|
|
fullscreen_flag = SDL_WINDOW_FULLSCREEN;
|
|
}
|
|
|
|
Uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL;
|
|
|
|
// if fullscreen was requested, start the window in fullscreen right away
|
|
if (set->fullscreen) {
|
|
flags |= fullscreen_flag;
|
|
fullscreen_state = true;
|
|
}
|
|
|
|
if (set->maximized) {
|
|
flags |= SDL_WINDOW_MAXIMIZED;
|
|
maximized_state = true;
|
|
}
|
|
|
|
#ifdef SDL_WINDOW_ALLOW_HIGHDPI
|
|
if (set->allow_hidpi) {
|
|
flags |= SDL_WINDOW_ALLOW_HIGHDPI;
|
|
}
|
|
#endif
|
|
|
|
wnd = SDL_CreateWindow(set->title, posX, posY, window_width, window_height, flags);
|
|
if (!wnd) {
|
|
sysFatalError("Could not open SDL window:\n%s", SDL_GetError());
|
|
}
|
|
|
|
// ideally we need 3.0 compat
|
|
// if that doesn't work, try 3.2 core in case we're on mac, 2.1 compat as a last resort
|
|
static u32 glver[][3] = {
|
|
{ 0, 0, 0 }, // for command line override
|
|
{ 3, 0, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY }, // 3.0: default, has all the features required
|
|
{ 4, 1, SDL_GL_CONTEXT_PROFILE_CORE }, // 4.1core: macs only have core profile and this is the latest
|
|
{ 3, 2, SDL_GL_CONTEXT_PROFILE_CORE }, // 3.2core: older macs will only have this at best
|
|
{ 3, 0, SDL_GL_CONTEXT_PROFILE_ES }, // es3: don't really support ES properly, but we can try
|
|
{ 2, 1, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY }, // 2.1: absolute last resort, will still require GLSL130 as an extension
|
|
};
|
|
|
|
u32 verstart = 1;
|
|
const u32 verend = sizeof(glver) / sizeof(*glver);
|
|
const char *verstr = sysArgGetString("--gl-version");
|
|
if (verstr && *verstr) {
|
|
// user override
|
|
glver[0][2] = strstr(verstr, "core") ? SDL_GL_CONTEXT_PROFILE_CORE :
|
|
(strstr(verstr, "es") ? SDL_GL_CONTEXT_PROFILE_ES :
|
|
SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
|
|
sscanf(verstr, "%d.%d", &glver[0][0], &glver[0][1]);
|
|
if (glver[0][0] >= 1 && glver[0][0] <= 4 && glver[0][1] < 9) {
|
|
verstart = 0;
|
|
}
|
|
}
|
|
|
|
ctx = NULL;
|
|
u32 vmin = 0, vmaj = 0, vprof = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
|
|
const char *vprofstr = "";
|
|
for (u32 i = verstart; i < verend; ++i) {
|
|
vmaj = glver[i][0];
|
|
vmin = glver[i][1];
|
|
vprof = glver[i][2];
|
|
vprofstr = (glver[i][2] == SDL_GL_CONTEXT_PROFILE_CORE ? "core" :
|
|
(glver[i][2] == SDL_GL_CONTEXT_PROFILE_ES ? "es" : ""));
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, vmaj);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, vmin);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, glver[i][2]);
|
|
ctx = SDL_GL_CreateContext(wnd);
|
|
if (!ctx) {
|
|
sysLogPrintf(LOG_WARNING, "GL: could not create GL%d.%d%s context: %s", vmaj, vmin, vprofstr, SDL_GetError());
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ctx) {
|
|
sysFatalError("Could not create an OpenGL context of any supported version.\nSDL error: %s", SDL_GetError());
|
|
} else {
|
|
sysLogPrintf(LOG_NOTE, "GL: created GL%d.%d%s context", vmaj, vmin, vprofstr);
|
|
}
|
|
|
|
SDL_GL_MakeCurrent(wnd, ctx);
|
|
SDL_GL_SetSwapInterval(1);
|
|
|
|
qpc_freq = SDL_GetPerformanceFrequency();
|
|
}
|
|
|
|
static void gfx_sdl_close(void) {
|
|
is_running = false;
|
|
}
|
|
|
|
static void gfx_sdl_set_fullscreen_changed_callback(void (*on_fullscreen_changed)(bool is_now_fullscreen)) {
|
|
on_fullscreen_changed_callback = on_fullscreen_changed;
|
|
}
|
|
|
|
static void gfx_sdl_set_fullscreen(bool enable) {
|
|
set_fullscreen(enable, true);
|
|
}
|
|
|
|
static void gfx_sdl_set_fullscreen_exclusive(bool enable) {
|
|
const uint32_t newflag = enable ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
|
if (fullscreen_flag != newflag) {
|
|
fullscreen_flag = newflag;
|
|
// reset fullscreen to take new value into account if it already is in fullscreen
|
|
if (fullscreen_state) {
|
|
fullscreen_state = false;
|
|
set_fullscreen(enable, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void gfx_sdl_set_maximize_window(bool enable) {
|
|
set_maximize_window(enable);
|
|
}
|
|
|
|
static void gfx_sdl_set_cursor_visibility(bool visible) {
|
|
if (visible) {
|
|
SDL_ShowCursor(SDL_ENABLE);
|
|
} else {
|
|
SDL_ShowCursor(SDL_DISABLE);
|
|
}
|
|
}
|
|
|
|
static void get_centered_positions_native(int32_t width, int32_t height, int32_t *posX, int32_t *posY) {
|
|
const int disp_idx = SDL_GetWindowDisplayIndex(wnd);
|
|
SDL_DisplayMode mode = {};
|
|
SDL_GetDesktopDisplayMode(disp_idx, &mode);
|
|
*posX = mode.w / 2 - width / 2;
|
|
*posY = mode.h / 2 - height / 2;
|
|
}
|
|
|
|
static void gfx_sdl_get_centered_positions(int32_t width, int32_t height, int32_t *posX, int32_t *posY) {
|
|
const int disp_idx = SDL_GetWindowDisplayIndex(wnd);
|
|
SDL_DisplayMode mode = {};
|
|
SDL_GetCurrentDisplayMode(disp_idx, &mode);
|
|
*posX = mode.w / 2 - width / 2;
|
|
*posY = mode.h / 2 - height / 2;
|
|
}
|
|
|
|
static void gfx_sdl_set_closest_resolution(int32_t width, int32_t height, bool should_center) {
|
|
const SDL_DisplayMode mode = {.w = width, .h = height};
|
|
const int disp_idx = SDL_GetWindowDisplayIndex(wnd);
|
|
SDL_DisplayMode closest = {};
|
|
if (SDL_GetClosestDisplayMode(disp_idx, &mode, &closest)) {
|
|
SDL_SetWindowDisplayMode(wnd, &closest);
|
|
SDL_SetWindowSize(wnd, closest.w, closest.h);
|
|
if (should_center) {
|
|
int32_t posX = 0;
|
|
int32_t posY = 0;
|
|
get_centered_positions_native(closest.w, closest.h, &posX, &posY);
|
|
SDL_SetWindowPosition(wnd, posX, posY);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void gfx_sdl_set_dimensions(uint32_t width, uint32_t height, int32_t posX, int32_t posY) {
|
|
SDL_SetWindowSize(wnd, width, height);
|
|
SDL_SetWindowPosition(wnd, posX, posY);
|
|
}
|
|
|
|
static void gfx_sdl_get_dimensions(uint32_t* width, uint32_t* height, int32_t* posX, int32_t* posY) {
|
|
SDL_GL_GetDrawableSize(wnd, static_cast<int*>((void*)width), static_cast<int*>((void*)height));
|
|
SDL_GetWindowPosition(wnd, static_cast<int*>(posX), static_cast<int*>(posY));
|
|
}
|
|
|
|
static void gfx_sdl_handle_events(void) {
|
|
SDL_Event event;
|
|
while (SDL_PollEvent(&event)) {
|
|
switch (event.type) {
|
|
case SDL_KEYDOWN:
|
|
if (event.key.keysym.sym == SDLK_RETURN && (event.key.keysym.mod & KMOD_ALT)) {
|
|
// alt-enter received, switch fullscreen state
|
|
set_fullscreen(!fullscreen_state, true);
|
|
}
|
|
break;
|
|
case SDL_WINDOWEVENT:
|
|
if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
|
|
SDL_GL_GetDrawableSize(wnd, &window_width, &window_height);
|
|
if (!fullscreen_state) {
|
|
maximized_state = SDL_GetWindowFlags(wnd) & SDL_WINDOW_MAXIMIZED ? true : false;
|
|
}
|
|
} else if (event.window.event == SDL_WINDOWEVENT_CLOSE &&
|
|
event.window.windowID == SDL_GetWindowID(wnd)) {
|
|
// We listen specifically for main window close because closing main window
|
|
// on macOS does not trigger SDL_Quit.
|
|
exit(0);
|
|
}
|
|
break;
|
|
case SDL_QUIT:
|
|
exit(0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool gfx_sdl_start_frame(void) {
|
|
return true;
|
|
}
|
|
|
|
static uint64_t qpc_to_100ns(uint64_t qpc) {
|
|
return qpc / qpc_freq * 10000000 + qpc % qpc_freq * 10000000 / qpc_freq;
|
|
}
|
|
|
|
static inline void sync_framerate_with_timer(void) {
|
|
uint64_t t;
|
|
t = qpc_to_100ns(SDL_GetPerformanceCounter());
|
|
|
|
const int64_t next = previous_time + 10 * FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR;
|
|
int64_t left = next - t;
|
|
// We want to exit a bit early, so we can busy-wait the rest to never miss the deadline
|
|
left -= 15000UL;
|
|
if (left > 0) {
|
|
sysSleep(left);
|
|
}
|
|
|
|
do {
|
|
sysCpuRelax();
|
|
t = qpc_to_100ns(SDL_GetPerformanceCounter());
|
|
} while ((int64_t)t < next);
|
|
|
|
t = qpc_to_100ns(SDL_GetPerformanceCounter());
|
|
if (left > 0 && t - next < 10000) {
|
|
// In case it takes some time for the application to wake up after sleep,
|
|
// or inaccurate timer,
|
|
// don't let that slow down the framerate.
|
|
t = next;
|
|
}
|
|
previous_time = t;
|
|
}
|
|
|
|
static void gfx_sdl_swap_buffers_begin(void) {
|
|
if (target_fps) {
|
|
sync_framerate_with_timer();
|
|
}
|
|
SDL_GL_SwapWindow(wnd);
|
|
}
|
|
|
|
static void gfx_sdl_swap_buffers_end(void) {
|
|
|
|
}
|
|
|
|
static double gfx_sdl_get_time(void) {
|
|
return SDL_GetPerformanceCounter() / (double)qpc_freq;
|
|
}
|
|
|
|
static int32_t gfx_sdl_get_target_fps(void) {
|
|
return target_fps;
|
|
}
|
|
|
|
static void gfx_sdl_set_target_fps(int fps) {
|
|
target_fps = fps;
|
|
}
|
|
|
|
static bool gfx_sdl_can_disable_vsync(void) {
|
|
return true;
|
|
}
|
|
|
|
static void *gfx_sdl_get_window_handle(void) {
|
|
return (void *)wnd;
|
|
}
|
|
|
|
static void gfx_sdl_set_window_title(const char *title) {
|
|
SDL_SetWindowTitle(wnd, title);
|
|
}
|
|
|
|
static int gfx_sdl_get_swap_interval(void) {
|
|
return SDL_GL_GetSwapInterval();
|
|
}
|
|
|
|
static bool gfx_sdl_set_swap_interval(int interval) {
|
|
const bool success = SDL_GL_SetSwapInterval(interval) >= 0;
|
|
vsync_enabled = success && (interval != 0);
|
|
if (!success) {
|
|
sysLogPrintf(LOG_WARNING, "SDL: failed to set vsync %d: %s", interval, SDL_GetError());
|
|
}
|
|
return success;
|
|
}
|
|
|
|
int gfx_sdl_get_display_mode(int modenum, int *out_w, int *out_h) {
|
|
const int display_in_use = SDL_GetWindowDisplayIndex(wnd);
|
|
SDL_DisplayMode sdlmode;
|
|
if (SDL_GetDisplayMode(display_in_use, modenum, &sdlmode) == 0) {
|
|
*out_w = sdlmode.w;
|
|
*out_h = sdlmode.h;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int gfx_sdl_get_current_display_mode(int *out_w, int *out_h) {
|
|
const int display_in_use = SDL_GetWindowDisplayIndex(wnd);
|
|
SDL_DisplayMode sdlmode;
|
|
if (SDL_GetCurrentDisplayMode(display_in_use, &sdlmode) == 0) {
|
|
*out_w = sdlmode.w;
|
|
*out_h = sdlmode.h;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int gfx_sdl_get_num_display_modes(void) {
|
|
const int display_in_use = SDL_GetWindowDisplayIndex(wnd);
|
|
return SDL_GetNumDisplayModes(display_in_use);
|
|
}
|
|
|
|
struct GfxWindowManagerAPI gfx_sdl = {
|
|
gfx_sdl_init,
|
|
gfx_sdl_close,
|
|
gfx_sdl_get_display_mode,
|
|
gfx_sdl_get_current_display_mode,
|
|
gfx_sdl_get_num_display_modes,
|
|
gfx_sdl_get_fullscreen_state,
|
|
gfx_sdl_set_fullscreen_changed_callback,
|
|
gfx_sdl_set_fullscreen,
|
|
gfx_sdl_set_fullscreen_exclusive,
|
|
gfx_sdl_set_fullscreen_flag,
|
|
gfx_sdl_get_fullscreen_flag_mode,
|
|
gfx_sdl_get_maximized_state,
|
|
gfx_sdl_set_maximize_window,
|
|
gfx_sdl_get_active_window_refresh_rate,
|
|
gfx_sdl_set_cursor_visibility,
|
|
gfx_sdl_set_closest_resolution,
|
|
gfx_sdl_set_dimensions,
|
|
gfx_sdl_get_dimensions,
|
|
gfx_sdl_get_centered_positions,
|
|
gfx_sdl_handle_events,
|
|
gfx_sdl_start_frame,
|
|
gfx_sdl_swap_buffers_begin,
|
|
gfx_sdl_swap_buffers_end,
|
|
gfx_sdl_get_time,
|
|
gfx_sdl_get_target_fps,
|
|
gfx_sdl_set_target_fps,
|
|
gfx_sdl_can_disable_vsync,
|
|
gfx_sdl_get_window_handle,
|
|
gfx_sdl_set_window_title,
|
|
gfx_sdl_get_swap_interval,
|
|
gfx_sdl_set_swap_interval,
|
|
};
|