#include #include #ifdef __MINGW32__ // mingw has its nanosleep implementation in pthread.h for some reson #include #else #include #include #endif #include "gfx_window_manager_api.h" #include "gfx_screen_config.h" #define GFX_BACKEND_NAME "SDL" 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 bool fullscreen_state; static bool is_running = true; static void (*on_fullscreen_changed_callback)(bool is_now_fullscreen); static uint64_t previous_time; static int target_fps = 120; // above 60 since vsync is enabled by default static uint64_t qpc_freq; #define FRAME_INTERVAL_US_NUMERATOR 1000000 #define FRAME_INTERVAL_US_DENOMINATOR (target_fps) static void set_fullscreen(bool on, bool call_callback) { if (fullscreen_state == on) { return; } fullscreen_state = on; SDL_SetWindowFullscreen(wnd, on ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); if (call_callback && on_fullscreen_changed_callback) { on_fullscreen_changed_callback(on); } } 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 inline void do_sleep(const uint64_t ns) { const timespec spec = { 0, ns }; nanosleep(&spec, nullptr); } static void gfx_sdl_init(const char* game_name, const char* gfx_api_name, bool start_in_fullscreen, uint32_t width, uint32_t height, int32_t posX, int32_t posY) { window_width = width; window_height = height; SDL_Init(SDL_INIT_VIDEO); SDL_EventState(SDL_DROPFILE, SDL_ENABLE); bool use_opengl = true; if (use_opengl) { SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); } SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); char title[512]; int len = sprintf(title, "%s (%s)", game_name, gfx_api_name); Uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; if (use_opengl) { flags = flags | SDL_WINDOW_OPENGL; } else { flags = flags | SDL_WINDOW_METAL; } int display_in_use = SDL_GetWindowDisplayIndex(wnd); if (display_in_use < 0) { // Fallback to default if out of bounds posX = 100; posY = 100; } wnd = SDL_CreateWindow(title, posX, posY, window_width, window_height, flags); if (use_opengl) { ctx = SDL_GL_CreateContext(wnd); SDL_GL_MakeCurrent(wnd, ctx); SDL_GL_SetSwapInterval(1); } qpc_freq = SDL_GetPerformanceFrequency(); set_fullscreen(start_in_fullscreen, false); } 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_cursor_visibility(bool visible) { if (visible) { SDL_ShowCursor(SDL_ENABLE); } else { SDL_ShowCursor(SDL_DISABLE); } } static void gfx_sdl_get_dimensions(uint32_t* width, uint32_t* height, int32_t* posX, int32_t* posY) { SDL_GL_GetDrawableSize(wnd, static_cast((void*)width), static_cast((void*)height)); SDL_GetWindowPosition(wnd, static_cast(posX), static_cast(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); } 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; const int64_t left = next - t; if (left > 0) { do_sleep(left * 100); } 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 void gfx_sdl_set_target_fps(int fps) { target_fps = fps; } static bool gfx_sdl_can_disable_vsync(void) { return false; } 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 void gfx_sdl_set_swap_interval(int interval) { SDL_GL_SetSwapInterval(interval); vsync_enabled = (interval != 0); } struct GfxWindowManagerAPI gfx_sdl = { gfx_sdl_init, gfx_sdl_close, gfx_sdl_set_fullscreen_changed_callback, gfx_sdl_set_fullscreen, gfx_sdl_get_active_window_refresh_rate, gfx_sdl_set_cursor_visibility, gfx_sdl_get_dimensions, gfx_sdl_handle_events, gfx_sdl_start_frame, gfx_sdl_swap_buffers_begin, gfx_sdl_swap_buffers_end, gfx_sdl_get_time, gfx_sdl_set_target_fps, gfx_sdl_can_disable_vsync, gfx_sdl_get_window_handle, gfx_sdl_set_window_title, gfx_sdl_set_swap_interval, };