/* RetroArch - A frontend for libretro. * Copyright (C) 2010-2012 - Hans-Kristian Arntzen * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with RetroArch. * If not, see . */ #include "../gfx_context.h" #include "../gfx_common.h" #include "../../general.h" #ifdef HAVE_X11 #include "x11_common.h" #endif #ifdef __APPLE__ #include #include #else #include "SDL/SDL_syswm.h" #endif #include "SDL.h" #include "../math/matrix.h" // SDL 1.2 is portable, sure, but you still need some platform specific workarounds ;) // Hopefully SDL 1.3 will solve this more cleanly :D // Welcome to #ifdef HELL. :D // #define GL_SYM_WRAP(symbol, proc) if (!symbol) { \ gfx_ctx_proc_t sym = gfx_ctx_get_proc_address(proc); \ memcpy(&(symbol), &sym, sizeof(sym)); \ } static bool g_fullscreen; static unsigned g_interval; static bool g_inited; static gfx_ctx_proc_t gfx_ctx_get_proc_address(const char *symbol); static void gfx_ctx_swap_interval(unsigned interval) { g_interval = interval; bool success = true; if (g_inited) { #if defined(_WIN32) static BOOL (APIENTRY *wgl_swap_interval)(int) = NULL; if (!wgl_swap_interval) GL_SYM_WRAP(wgl_swap_interval, "wglSwapIntervalEXT"); if (wgl_swap_interval) success = wgl_swap_interval(g_interval); #elif defined(__APPLE__) && defined(HAVE_OPENGL) GLint val = g_interval; success = CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &val) == 0; #else static int (*glx_swap_interval)(int) = NULL; if (!glx_swap_interval) GL_SYM_WRAP(glx_swap_interval, "glXSwapInterval"); if (!glx_swap_interval) GL_SYM_WRAP(glx_swap_interval, "glXSwapIntervalMESA"); if (!glx_swap_interval) GL_SYM_WRAP(glx_swap_interval, "glXSwapIntervalSGI"); if (glx_swap_interval) success = glx_swap_interval(g_interval) == 0; else RARCH_WARN("Could not find GLX VSync call.\n"); #endif } if (!success) RARCH_WARN("Failed to set swap interval.\n"); } static void gfx_ctx_wm_set_caption(const char *str) { SDL_WM_SetCaption(str, NULL); } static void gfx_ctx_update_window_title(bool reset) { if (reset) gfx_window_title_reset(); char buf[128]; if (gfx_window_title(buf, sizeof(buf))) gfx_ctx_wm_set_caption(buf); } static void gfx_ctx_get_video_size(unsigned *width, unsigned *height) { const SDL_VideoInfo *video_info = SDL_GetVideoInfo(); rarch_assert(video_info); *width = video_info->current_w; *height = video_info->current_h; } static bool gfx_ctx_init(void) { if (SDL_WasInit(SDL_INIT_VIDEO)) return true; bool ret = SDL_Init(SDL_INIT_VIDEO) == 0; if (!ret) RARCH_ERR("Failed to init SDL video.\n"); return ret; } static void gfx_ctx_destroy(void) { SDL_QuitSubSystem(SDL_INIT_VIDEO); g_inited = false; } static void sdl_set_handles(void) { #if defined(_WIN32) SDL_SysWMinfo info; SDL_VERSION(&info.version); if (SDL_GetWMInfo(&info) == 1) { driver.display_type = RARCH_DISPLAY_WIN32; driver.video_display = 0; driver.video_window = (uintptr_t)info.window; } #elif defined(HAVE_X11) SDL_SysWMinfo info; SDL_VERSION(&info.version); if (SDL_GetWMInfo(&info) == 1) { driver.display_type = RARCH_DISPLAY_X11; driver.video_display = (uintptr_t)info.info.x11.display; driver.video_window = (uintptr_t)info.info.x11.window; } #endif } static bool gfx_ctx_set_video_mode( unsigned width, unsigned height, unsigned bits, bool fullscreen) { #ifndef __APPLE__ // Resizing on OSX is broken in 1.2 it seems :) static const int resizable = SDL_RESIZABLE; #else static const int resizable = 0; #endif SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, g_interval); if (!SDL_SetVideoMode(width, height, bits, SDL_OPENGL | (fullscreen ? SDL_FULLSCREEN : resizable))) { RARCH_ERR("Failed to create SDL window.\n"); return false; } g_inited = true; int attr = 0; SDL_GL_GetAttribute(SDL_GL_SWAP_CONTROL, &attr); if (attr <= 0 && g_interval) { RARCH_WARN("SDL failed to setup VSync, attempting to recover using native calls.\n"); gfx_ctx_swap_interval(g_interval); } g_fullscreen = fullscreen; attr = 0; SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &attr); if (attr <= 0) RARCH_WARN("GL double buffer has not been enabled.\n"); // Remove that ugly mouse :D SDL_ShowCursor(SDL_DISABLE); sdl_set_handles(); return true; } // SDL 1.2 has an awkward model where you need to "confirm" window resizing. static void gfx_ctx_set_resize(unsigned width, unsigned height) { #ifndef __APPLE__ // Resizing on OSX is broken in 1.2 it seems :) static const int resizable = SDL_RESIZABLE; #else static const int resizable = 0; #endif SDL_SetVideoMode(width, height, 0, SDL_OPENGL | (g_fullscreen ? SDL_FULLSCREEN : resizable)); } static void gfx_ctx_swap_buffers(void) { SDL_GL_SwapBuffers(); } // 1.2 specific workaround for tiling WMs. #if defined(HAVE_X11) // This X11 is set on OSX for some reason. static bool gfx_ctx_get_window_size(unsigned *width, unsigned *height) { SDL_SysWMinfo info; SDL_VERSION(&info.version); if (SDL_GetWMInfo(&info) != 1) return false; XWindowAttributes target; info.info.x11.lock_func(); XGetWindowAttributes(info.info.x11.display, info.info.x11.window, &target); info.info.x11.unlock_func(); *width = target.width; *height = target.height; return true; } #endif static void gfx_ctx_check_window(bool *quit, bool *resize, unsigned *width, unsigned *height, unsigned frame_count) { *quit = false; *resize = false; SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: *quit = true; break; case SDL_VIDEORESIZE: *resize = true; *width = event.resize.w; *height = event.resize.h; break; } } #if defined(HAVE_X11) if (!*resize && !g_fullscreen) { unsigned new_width, new_height; // Hack to workaround bugs in tiling WMs ... Very ugly. // We trigger a resize, to force a resize event to occur. // Some tiling WMs will immediately change window size, and not trigger an event in SDL to notify that the window // size has in fact, changed. // By forcing a dummy resize to original size, we can make sure events are triggered. if (gfx_ctx_get_window_size(&new_width, &new_height) && ((new_width != *width || new_height != *height) || (frame_count == 10))) // 10 here is chosen arbitrarily. { *resize = true; *width = new_width; *height = new_height; RARCH_LOG("GL: Verified window size: %u x %u\n", *width, *height); } } #endif } static bool gfx_ctx_has_focus(void) { return (SDL_GetAppState() & (SDL_APPINPUTFOCUS | SDL_APPACTIVE)) == (SDL_APPINPUTFOCUS | SDL_APPACTIVE); } static void gfx_ctx_input_driver(const input_driver_t **input, void **input_data) { void *sdl_input = input_sdl.init(); if (sdl_input) { *input = &input_sdl; *input_data = sdl_input; } else *input = NULL; } // Enforce void (*)(void) as it's not really legal to cast void* to fn-pointer. // POSIX allows this, but strict C99 doesn't. static gfx_ctx_proc_t gfx_ctx_get_proc_address(const char *symbol) { // This will not fail on any system RetroArch would run on, but let's just be defensive. rarch_assert(sizeof(void*) == sizeof(void (*)(void))); gfx_ctx_proc_t ret; void *sym__ = SDL_GL_GetProcAddress(symbol); memcpy(&ret, &sym__, sizeof(void*)); return ret; } static bool gfx_ctx_bind_api(enum gfx_ctx_api api) { return api == GFX_CTX_OPENGL_API; } const gfx_ctx_driver_t gfx_ctx_sdl_gl = { gfx_ctx_init, gfx_ctx_destroy, gfx_ctx_bind_api, gfx_ctx_swap_interval, gfx_ctx_set_video_mode, gfx_ctx_get_video_size, NULL, gfx_ctx_update_window_title, gfx_ctx_check_window, gfx_ctx_set_resize, gfx_ctx_has_focus, gfx_ctx_swap_buffers, gfx_ctx_input_driver, gfx_ctx_get_proc_address, "sdl-gl", };