added support for (normal and adaptive) vertical sync (vsync)
[rocksndiamonds.git] / src / libgame / sdl.c
index 5586628b40dabf1385a5ae09472828f079e5501b..b03f01adab1a1bd3026f53ef1e8e8a234cd35905 100644 (file)
@@ -17,6 +17,8 @@
 
 #define ENABLE_UNUSED_CODE     0       /* currently unused functions */
 
+#define DEBUG_JOYSTICKS                0
+
 
 /* ========================================================================= */
 /* video functions                                                           */
@@ -60,6 +62,10 @@ static void FinalizeScreen(int draw_target)
   // copy global animations to render target buffer, if defined (above border)
   if (gfx.draw_global_anim_function != NULL)
     gfx.draw_global_anim_function(draw_target, DRAW_GLOBAL_ANIM_STAGE_2);
+
+  // copy tile selection cursor to render target buffer, if defined (above all)
+  if (gfx.draw_tile_cursor_function != NULL)
+    gfx.draw_tile_cursor_function(draw_target);
 }
 
 static void UpdateScreenExt(SDL_Rect *rect, boolean with_frame_delay)
@@ -239,6 +245,20 @@ static void UpdateScreen_WithoutFrameDelay(SDL_Rect *rect)
   UpdateScreenExt(rect, FALSE);
 }
 
+void Delay_WithScreenUpdates(unsigned int delay)
+{
+  unsigned int time_current = SDL_GetTicks();
+  unsigned int time_delayed = time_current + delay;
+
+  while (time_current < time_delayed)
+  {
+    // updating the screen contains waiting for frame delay (non-busy)
+    UpdateScreen_WithFrameDelay(NULL);
+
+    time_current = SDL_GetTicks();
+  }
+}
+
 static void SDLSetWindowIcon(char *basename)
 {
   /* (setting the window icon on Mac OS X would replace the high-quality
@@ -532,14 +552,10 @@ inline static void SDLInitVideoBuffer_VideoBuffer(boolean fullscreen)
   SDLSetWindowIcon(program.icon_filename);
 
   /* set window and icon title */
-#if defined(TARGET_SDL2)
-  SDL_SetWindowTitle(sdl_window, program.window_title);
-#else
-  SDL_WM_SetCaption(program.window_title, program.window_title);
-#endif
+  SDLSetWindowTitle();
 }
 
-inline static void SDLInitVideoBuffer_DrawBuffer()
+inline static void SDLInitVideoBuffer_DrawBuffer(void)
 {
   /* SDL cannot directly draw to the visible video framebuffer like X11,
      but always uses a backbuffer, which is then blitted to the visible
@@ -661,6 +677,8 @@ static boolean SDLCreateScreen(boolean fullscreen)
       // SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
       SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, setup.window_scaling_quality);
 
+      SDLSetScreenVsyncMode(setup.vsync_mode);
+
       sdl_texture_stream = SDL_CreateTexture(sdl_renderer,
                                             SDL_PIXELFORMAT_ARGB8888,
                                             SDL_TEXTUREACCESS_STREAMING,
@@ -827,9 +845,12 @@ boolean SDLSetVideoMode(boolean fullscreen)
   return success;
 }
 
-void SDLSetWindowTitle()
+void SDLSetWindowTitle(void)
 {
 #if defined(TARGET_SDL2)
+  if (sdl_window == NULL)
+    return;
+
   SDL_SetWindowTitle(sdl_window, program.window_title);
 #else
   SDL_WM_SetCaption(program.window_title, program.window_title);
@@ -917,7 +938,7 @@ void SDLSetWindowFullscreen(boolean fullscreen)
   }
 }
 
-void SDLSetDisplaySize()
+void SDLSetDisplaySize(void)
 {
   SDL_Rect display_bounds;
 
@@ -971,7 +992,7 @@ void SDLSetScreenSizeForRenderer(int width, int height)
   SDL_RenderSetLogicalSize(sdl_renderer, width, height);
 }
 
-void SDLSetScreenProperties()
+void SDLSetScreenProperties(void)
 {
   SDLSetScreenSizeAndOffsets(video.width, video.height);
   SDLSetScreenSizeForRenderer(video.screen_width, video.screen_height);
@@ -994,7 +1015,22 @@ void SDLSetScreenRenderingMode(char *screen_rendering_mode)
 #endif
 }
 
-void SDLRedrawWindow()
+void SDLSetScreenVsyncMode(char *vsync_mode)
+{
+#if defined(TARGET_SDL2)
+  int interval =
+    (strEqual(vsync_mode, STR_VSYNC_MODE_NORMAL)   ? VSYNC_MODE_NORMAL :
+     strEqual(vsync_mode, STR_VSYNC_MODE_ADAPTIVE) ? VSYNC_MODE_ADAPTIVE :
+     VSYNC_MODE_OFF);
+  int result = SDL_GL_SetSwapInterval(interval);
+
+  // if adaptive vsync requested, but not supported, retry with normal vsync
+  if (result == -1 && interval == VSYNC_MODE_ADAPTIVE)
+    SDL_GL_SetSwapInterval(VSYNC_MODE_NORMAL);
+#endif
+}
+
+void SDLRedrawWindow(void)
 {
   UpdateScreen_WithoutFrameDelay(NULL);
 }
@@ -1384,20 +1420,7 @@ void SDLFadeRectangle(int x, int y, int width, int height,
   }
 
   if (post_delay > 0)
-  {
-    unsigned int time_post_delay;
-
-    time_current = SDL_GetTicks();
-    time_post_delay = time_current + post_delay;
-
-    while (time_current < time_post_delay)
-    {
-      // updating the screen contains waiting for frame delay (non-busy)
-      UpdateScreen_WithFrameDelay(NULL);
-
-      time_current = SDL_GetTicks();
-    }
-  }
+    Delay_WithScreenUpdates(post_delay);
 
   // restore function for drawing global masked border
   gfx.draw_global_border_function = draw_global_border_function;
@@ -1516,7 +1539,7 @@ Pixel SDLGetPixel(Bitmap *src_bitmap, int x, int y)
 /* http://www.etek.chalmers.se/~e8cal1/sge/index.html                        */
 /* ========================================================================= */
 
-void _PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
+static void _PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
 {
   if (x >= 0 && x <= surface->w - 1 && y >= 0 && y <= surface->h - 1)
   {
@@ -1563,23 +1586,24 @@ void _PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
   }
 }
 
-void _PutPixelRGB(SDL_Surface *surface, Sint16 x, Sint16 y,
-                 Uint8 R, Uint8 G, Uint8 B)
+#if 0
+static void _PutPixelRGB(SDL_Surface *surface, Sint16 x, Sint16 y,
+                        Uint8 R, Uint8 G, Uint8 B)
 {
   _PutPixel(surface, x, y, SDL_MapRGB(surface->format, R, G, B));
 }
 
-void _PutPixel8(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
+static void _PutPixel8(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
 {
   *((Uint8 *)surface->pixels + y*surface->pitch + x) = color;
 }
 
-void _PutPixel16(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
+static void _PutPixel16(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
 {
   *((Uint16 *)surface->pixels + y*surface->pitch/2 + x) = color;
 }
 
-void _PutPixel24(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
+static void _PutPixel24(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
 {
   Uint8 *pix;
   int shift;
@@ -1594,12 +1618,12 @@ void _PutPixel24(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
   *(pix+shift/8) = color>>shift;
 }
 
-void _PutPixel32(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
+static void _PutPixel32(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
 {
   *((Uint32 *)surface->pixels + y*surface->pitch/4 + x) = color;
 }
 
-void _PutPixelX(SDL_Surface *dest,Sint16 x,Sint16 y,Uint32 color)
+static void _PutPixelX(SDL_Surface *dest,Sint16 x,Sint16 y,Uint32 color)
 {
   switch (dest->format->BytesPerPixel)
   {
@@ -1620,8 +1644,9 @@ void _PutPixelX(SDL_Surface *dest,Sint16 x,Sint16 y,Uint32 color)
       break;
   }
 }
+#endif
 
-void sge_PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
+static void sge_PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
 {
   if (SDL_MUSTLOCK(surface))
   {
@@ -1639,13 +1664,14 @@ void sge_PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
   }
 }
 
-void sge_PutPixelRGB(SDL_Surface *surface, Sint16 x, Sint16 y,
-                 Uint8 r, Uint8 g, Uint8 b)
+#if 0
+static void sge_PutPixelRGB(SDL_Surface *surface, Sint16 x, Sint16 y,
+                           Uint8 r, Uint8 g, Uint8 b)
 {
   sge_PutPixel(surface, x, y, SDL_MapRGB(surface->format, r, g, b));
 }
 
-Sint32 sge_CalcYPitch(SDL_Surface *dest, Sint16 y)
+static Sint32 sge_CalcYPitch(SDL_Surface *dest, Sint16 y)
 {
   if (y >= 0 && y <= dest->h - 1)
   {
@@ -1672,7 +1698,8 @@ Sint32 sge_CalcYPitch(SDL_Surface *dest, Sint16 y)
   return -1;
 }
 
-void sge_pPutPixel(SDL_Surface *surface, Sint16 x, Sint32 ypitch, Uint32 color)
+static void sge_pPutPixel(SDL_Surface *surface, Sint16 x, Sint32 ypitch,
+                         Uint32 color)
 {
   if (x >= 0 && x <= surface->w - 1 && ypitch >= 0)
   {
@@ -1719,8 +1746,8 @@ void sge_pPutPixel(SDL_Surface *surface, Sint16 x, Sint32 ypitch, Uint32 color)
   }
 }
 
-void sge_HLine(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
-              Uint32 Color)
+static void sge_HLine(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
+                     Uint32 Color)
 {
   SDL_Rect l;
 
@@ -1760,13 +1787,14 @@ void sge_HLine(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
   }
 }
 
-void sge_HLineRGB(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
-                 Uint8 R, Uint8 G, Uint8 B)
+static void sge_HLineRGB(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
+                        Uint8 R, Uint8 G, Uint8 B)
 {
   sge_HLine(Surface, x1, x2, y, SDL_MapRGB(Surface->format, R, G, B));
 }
 
-void _HLine(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y, Uint32 Color)
+static void _HLine(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
+                  Uint32 Color)
 {
   SDL_Rect l;
 
@@ -1793,8 +1821,8 @@ void _HLine(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y, Uint32 Color)
   SDL_FillRect(Surface, &l, Color);
 }
 
-void sge_VLine(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
-              Uint32 Color)
+static void sge_VLine(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
+                     Uint32 Color)
 {
   SDL_Rect l;
 
@@ -1834,13 +1862,14 @@ void sge_VLine(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
   }
 }
 
-void sge_VLineRGB(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
-                 Uint8 R, Uint8 G, Uint8 B)
+static void sge_VLineRGB(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
+                        Uint8 R, Uint8 G, Uint8 B)
 {
   sge_VLine(Surface, x, y1, y2, SDL_MapRGB(Surface->format, R, G, B));
 }
 
-void _VLine(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2, Uint32 Color)
+static void _VLine(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
+                  Uint32 Color)
 {
   SDL_Rect l;
 
@@ -1866,11 +1895,12 @@ void _VLine(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2, Uint32 Color)
 
   SDL_FillRect(Surface, &l, Color);
 }
+#endif
 
-void sge_DoLine(SDL_Surface *Surface, Sint16 x1, Sint16 y1,
-               Sint16 x2, Sint16 y2, Uint32 Color,
-               void Callback(SDL_Surface *Surf, Sint16 X, Sint16 Y,
-                             Uint32 Color))
+static void sge_DoLine(SDL_Surface *Surface, Sint16 x1, Sint16 y1,
+                      Sint16 x2, Sint16 y2, Uint32 Color,
+                      void Callback(SDL_Surface *Surf, Sint16 X, Sint16 Y,
+                                    Uint32 Color))
 {
   Sint16 dx, dy, sdx, sdy, x, y, px, py;
 
@@ -1922,14 +1952,16 @@ void sge_DoLine(SDL_Surface *Surface, Sint16 x1, Sint16 y1,
   }
 }
 
-void sge_DoLineRGB(SDL_Surface *Surface, Sint16 X1, Sint16 Y1,
-                  Sint16 X2, Sint16 Y2, Uint8 R, Uint8 G, Uint8 B,
-                  void Callback(SDL_Surface *Surf, Sint16 X, Sint16 Y,
-                                Uint32 Color))
+#if 0
+static void sge_DoLineRGB(SDL_Surface *Surface, Sint16 X1, Sint16 Y1,
+                         Sint16 X2, Sint16 Y2, Uint8 R, Uint8 G, Uint8 B,
+                         void Callback(SDL_Surface *Surf, Sint16 X, Sint16 Y,
+                                       Uint32 Color))
 {
   sge_DoLine(Surface, X1, Y1, X2, Y2,
             SDL_MapRGB(Surface->format, R, G, B), Callback);
 }
+#endif
 
 void sge_Line(SDL_Surface *Surface, Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2,
              Uint32 Color)
@@ -1950,11 +1982,13 @@ void sge_Line(SDL_Surface *Surface, Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2,
    }
 }
 
-void sge_LineRGB(SDL_Surface *Surface, Sint16 x1, Sint16 y1, Sint16 x2,
-                Sint16 y2, Uint8 R, Uint8 G, Uint8 B)
+#if 0
+static void sge_LineRGB(SDL_Surface *Surface, Sint16 x1, Sint16 y1, Sint16 x2,
+                       Sint16 y2, Uint8 R, Uint8 G, Uint8 B)
 {
   sge_Line(Surface, x1, y1, x2, y2, SDL_MapRGB(Surface->format, R, G, B));
 }
+#endif
 
 void SDLPutPixel(Bitmap *dst_bitmap, int x, int y, Pixel pixel)
 {
@@ -2025,7 +2059,7 @@ typedef struct
   Uint8 a;
 } tColorRGBA;
 
-int zoomSurfaceRGBA_scaleDownBy2(SDL_Surface *src, SDL_Surface *dst)
+static int zoomSurfaceRGBA_scaleDownBy2(SDL_Surface *src, SDL_Surface *dst)
 {
   int x, y;
   tColorRGBA *sp, *csp, *dp;
@@ -2076,7 +2110,7 @@ int zoomSurfaceRGBA_scaleDownBy2(SDL_Surface *src, SDL_Surface *dst)
   return 0;
 }
 
-int zoomSurfaceRGBA(SDL_Surface *src, SDL_Surface *dst)
+static int zoomSurfaceRGBA(SDL_Surface *src, SDL_Surface *dst)
 {
   int x, y, *sax, *say, *csax, *csay;
   float sx, sy;
@@ -2149,7 +2183,7 @@ int zoomSurfaceRGBA(SDL_Surface *src, SDL_Surface *dst)
   -----------------------------------------------------------------------------
 */
 
-int zoomSurfaceY(SDL_Surface * src, SDL_Surface * dst)
+static int zoomSurfaceY(SDL_Surface * src, SDL_Surface * dst)
 {
   Uint32 x, y, sx, sy, *sax, *say, *csax, *csay, csx, csy;
   Uint8 *sp, *dp, *csp;
@@ -2249,7 +2283,7 @@ int zoomSurfaceY(SDL_Surface * src, SDL_Surface * dst)
   -----------------------------------------------------------------------------
 */
 
-SDL_Surface *zoomSurface(SDL_Surface *src, int dst_width, int dst_height)
+static SDL_Surface *zoomSurface(SDL_Surface *src, int dst_width, int dst_height)
 {
   SDL_Surface *zoom_src = NULL;
   SDL_Surface *zoom_dst = NULL;
@@ -2535,7 +2569,7 @@ void SDLCloseAudio(void)
 /* event functions                                                           */
 /* ========================================================================= */
 
-void SDLNextEvent(Event *event)
+void SDLWaitEvent(Event *event)
 {
   SDL_WaitEvent(event);
 }
@@ -2601,9 +2635,24 @@ static int sdl_js_axis[MAX_PLAYERS][2];
 static int sdl_js_button[MAX_PLAYERS][2];
 static boolean sdl_is_controller[MAX_PLAYERS];
 
-static boolean SDLOpenJoystick(int nr)
+void SDLClearJoystickState(void)
 {
-  if (nr < 0 || nr > MAX_PLAYERS)
+  int i, j;
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    for (j = 0; j < 2; j++)
+    {
+      sdl_js_axis_raw[i][j] = -1;
+      sdl_js_axis[i][j] = 0;
+      sdl_js_button[i][j] = 0;
+    }
+  }
+}
+
+boolean SDLOpenJoystick(int nr)
+{
+  if (nr < 0 || nr >= MAX_PLAYERS)
     return FALSE;
 
 #if defined(TARGET_SDL2)
@@ -2612,7 +2661,7 @@ static boolean SDLOpenJoystick(int nr)
   sdl_is_controller[nr] = FALSE;
 #endif
 
-#if 1
+#if DEBUG_JOYSTICKS
   Error(ERR_DEBUG, "opening joystick %d (%s)",
        nr, (sdl_is_controller[nr] ? "game controller" : "joystick"));
 #endif
@@ -2629,12 +2678,12 @@ static boolean SDLOpenJoystick(int nr)
   return (sdl_joystick[nr] != NULL);
 }
 
-static void SDLCloseJoystick(int nr)
+void SDLCloseJoystick(int nr)
 {
-  if (nr < 0 || nr > MAX_PLAYERS)
+  if (nr < 0 || nr >= MAX_PLAYERS)
     return;
 
-#if 1
+#if DEBUG_JOYSTICKS
   Error(ERR_DEBUG, "closing joystick %d", nr);
 #endif
 
@@ -2648,18 +2697,11 @@ static void SDLCloseJoystick(int nr)
 #endif
 
   sdl_joystick[nr] = NULL;
-
-  sdl_js_axis_raw[nr][0] = -1;
-  sdl_js_axis_raw[nr][1] = -1;
-  sdl_js_axis[nr][0] = 0;
-  sdl_js_axis[nr][1] = 0;
-  sdl_js_button[nr][0] = 0;
-  sdl_js_button[nr][1] = 0;
 }
 
 boolean SDLCheckJoystickOpened(int nr)
 {
-  if (nr < 0 || nr > MAX_PLAYERS)
+  if (nr < 0 || nr >= MAX_PLAYERS)
     return FALSE;
 
 #if defined(TARGET_SDL2)
@@ -2680,6 +2722,9 @@ static void setJoystickAxis(int nr, int axis_id_raw, int axis_value)
   int axis_id = axis_id_raw % 2;
 #endif
 
+  if (nr < 0 || nr >= MAX_PLAYERS)
+    return;
+
   if (axis_id == -1)
     return;
 
@@ -2723,6 +2768,9 @@ static void setJoystickButton(int nr, int button_id_raw, int button_state)
   int button_id = button_id_raw % 2;
 #endif
 
+  if (nr < 0 || nr >= MAX_PLAYERS)
+    return;
+
   if (button_id == -1)
     return;
 
@@ -2734,8 +2782,24 @@ void HandleJoystickEvent(Event *event)
   switch(event->type)
   {
 #if defined(TARGET_SDL2)
+    case SDL_CONTROLLERDEVICEADDED:
+#if DEBUG_JOYSTICKS
+      Error(ERR_DEBUG, "SDL_CONTROLLERDEVICEADDED: device %d added",
+           event->cdevice.which);
+#endif
+      InitJoysticks();
+      break;
+
+    case SDL_CONTROLLERDEVICEREMOVED:
+#if DEBUG_JOYSTICKS
+      Error(ERR_DEBUG, "SDL_CONTROLLERDEVICEREMOVED: device %d removed",
+           event->cdevice.which);
+#endif
+      InitJoysticks();
+      break;
+
     case SDL_CONTROLLERAXISMOTION:
-#if 1
+#if DEBUG_JOYSTICKS
       Error(ERR_DEBUG, "SDL_CONTROLLERAXISMOTION: device %d, axis %d: %d",
            event->caxis.which, event->caxis.axis, event->caxis.value);
 #endif
@@ -2745,7 +2809,7 @@ void HandleJoystickEvent(Event *event)
       break;
 
     case SDL_CONTROLLERBUTTONDOWN:
-#if 1
+#if DEBUG_JOYSTICKS
       Error(ERR_DEBUG, "SDL_CONTROLLERBUTTONDOWN: device %d, button %d",
            event->cbutton.which, event->cbutton.button);
 #endif
@@ -2755,7 +2819,7 @@ void HandleJoystickEvent(Event *event)
       break;
 
     case SDL_CONTROLLERBUTTONUP:
-#if 1
+#if DEBUG_JOYSTICKS
       Error(ERR_DEBUG, "SDL_CONTROLLERBUTTONUP: device %d, button %d",
            event->cbutton.which, event->cbutton.button);
 #endif
@@ -2769,7 +2833,7 @@ void HandleJoystickEvent(Event *event)
       if (sdl_is_controller[event->jaxis.which])
        break;
 
-#if 1
+#if DEBUG_JOYSTICKS
       Error(ERR_DEBUG, "SDL_JOYAXISMOTION: device %d, axis %d: %d",
            event->jaxis.which, event->jaxis.axis, event->jaxis.value);
 #endif
@@ -2783,7 +2847,7 @@ void HandleJoystickEvent(Event *event)
       if (sdl_is_controller[event->jaxis.which])
        break;
 
-#if 1
+#if DEBUG_JOYSTICKS
       Error(ERR_DEBUG, "SDL_JOYBUTTONDOWN: device %d, button %d",
            event->jbutton.which, event->jbutton.button);
 #endif
@@ -2797,7 +2861,7 @@ void HandleJoystickEvent(Event *event)
       if (sdl_is_controller[event->jaxis.which])
        break;
 
-#if 1
+#if DEBUG_JOYSTICKS
       Error(ERR_DEBUG, "SDL_JOYBUTTONUP: device %d, button %d",
            event->jbutton.which, event->jbutton.button);
 #endif
@@ -2812,12 +2876,15 @@ void HandleJoystickEvent(Event *event)
   }
 }
 
-void SDLInitJoysticks()
+void SDLInitJoysticks(void)
 {
   static boolean sdl_joystick_subsystem_initialized = FALSE;
   boolean print_warning = !sdl_joystick_subsystem_initialized;
 #if defined(TARGET_SDL2)
-  char *mappings_file = "gamecontrollerdb.txt";
+  char *mappings_file_base = getPath2(options.conf_directory,
+                                     GAMECONTROLLER_BASENAME);
+  char *mappings_file_user = getPath2(getUserGameDataDir(),
+                                     GAMECONTROLLER_BASENAME);
   int num_mappings;
 #endif
   int i;
@@ -2839,15 +2906,32 @@ void SDLInitJoysticks()
     }
 
 #if defined(TARGET_SDL2)
-    num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file);
+    num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_base);
 
-    if (num_mappings != -1)
-      Error(ERR_INFO, "%d game controller mapping(s) added", num_mappings);
+    /* the included game controller base mappings should always be found */
+    if (num_mappings == -1)
+      Error(ERR_WARN, "no game controller base mappings found");
+#if DEBUG_JOYSTICKS
     else
-      Error(ERR_WARN, "no game controller mappings found");
+      Error(ERR_INFO, "%d game controller base mapping(s) added", num_mappings);
+#endif
+
+    num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_user);
+
+#if DEBUG_JOYSTICKS
+    /* the personal game controller user mappings may or may not be found */
+    if (num_mappings == -1)
+      Error(ERR_WARN, "no game controller user mappings found");
+    else
+      Error(ERR_INFO, "%d game controller user mapping(s) added", num_mappings);
 
     Error(ERR_INFO, "%d joystick(s) found:", SDL_NumJoysticks());
+#endif
+
+    checked_free(mappings_file_base);
+    checked_free(mappings_file_user);
 
+#if DEBUG_JOYSTICKS
     for (i = 0; i < SDL_NumJoysticks(); i++)
     {
       const char *name, *type;
@@ -2866,6 +2950,7 @@ void SDLInitJoysticks()
       Error(ERR_INFO, "- joystick %d (%s): '%s'",
            i, type, (name ? name : "(Unknown)"));
     }
+#endif
 #endif
   }
 
@@ -2884,8 +2969,8 @@ void SDLInitJoysticks()
       joystick_nr = -1;
     }
 
-    /* misuse joystick file descriptor variable to store joystick number */
-    joystick.fd[i] = joystick_nr;
+    /* store configured joystick number for each player */
+    joystick.nr[i] = joystick_nr;
   }
 
   /* now open all connected joysticks (regardless if configured or not) */
@@ -2900,6 +2985,8 @@ void SDLInitJoysticks()
     else if (print_warning)
       Error(ERR_WARN, "cannot open joystick %d", i);
   }
+
+  SDLClearJoystickState();
 }
 
 boolean SDLReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
@@ -2920,22 +3007,195 @@ boolean SDLReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
   return TRUE;
 }
 
+
+/* ========================================================================= */
+/* touch input overlay functions                                             */
+/* ========================================================================= */
+
 #if defined(USE_TOUCH_INPUT_OVERLAY)
-static void DrawTouchInputOverlay()
+static void DrawTouchInputOverlay_ShowGrid(int alpha)
+{
+  SDL_Rect rect;
+  int grid_xsize = overlay.grid_xsize;
+  int grid_ysize = overlay.grid_ysize;
+  int x, y;
+
+  SDL_SetRenderDrawColor(sdl_renderer, 255, 255, 255, alpha);
+  SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
+
+  for (x = 0; x < grid_xsize; x++)
+  {
+    rect.x = (x + 0) * video.screen_width / grid_xsize;
+    rect.w = (x + 1) * video.screen_width / grid_xsize - rect.x;
+
+    for (y = 0; y < grid_ysize; y++)
+    {
+      rect.y = (y + 0) * video.screen_height / grid_ysize;
+      rect.h = (y + 1) * video.screen_height / grid_ysize - rect.y;
+
+      if (overlay.grid_button[x][y] == CHAR_GRID_BUTTON_NONE)
+       SDL_RenderDrawRect(sdl_renderer, &rect);
+    }
+  }
+
+  SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
+}
+
+static void RenderFillRectangle(int x, int y, int width, int height)
+{
+  SDL_Rect rect = { x, y, width, height };
+
+  SDL_RenderFillRect(sdl_renderer, &rect);
+}
+
+static void DrawTouchInputOverlay_ShowGridButtons(int alpha)
+{
+  static int alpha_direction = 0;
+  static int alpha_highlight = 0;
+  int alpha_max = ALPHA_FROM_TRANSPARENCY(setup.touch.transparency);
+  int alpha_step = ALPHA_FADING_STEPSIZE(alpha_max);
+  SDL_Rect rect;
+  int grid_xsize = overlay.grid_xsize;
+  int grid_ysize = overlay.grid_ysize;
+  int x, y;
+
+  if (alpha == alpha_max)
+  {
+    if (alpha_direction < 0)
+    {
+      alpha_highlight = MAX(0, alpha_highlight - alpha_step);
+
+      if (alpha_highlight == 0)
+       alpha_direction = 1;
+    }
+    else
+    {
+      alpha_highlight = MIN(alpha_highlight + alpha_step, alpha_max);
+
+      if (alpha_highlight == alpha_max)
+       alpha_direction = -1;
+    }
+  }
+  else
+  {
+    alpha_direction = 1;
+    alpha_highlight = alpha;
+  }
+
+  SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
+
+  for (x = 0; x < grid_xsize; x++)
+  {
+    for (y = 0; y < grid_ysize; y++)
+    {
+      int grid_button = overlay.grid_button[x][y];
+      int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
+      int alpha_draw = alpha;
+      int outline_border = MV_NONE;
+      int border_size = 2;
+      boolean draw_outlined = setup.touch.draw_outlined;
+      boolean draw_pressed = setup.touch.draw_pressed;
+
+      if (grid_button == CHAR_GRID_BUTTON_NONE)
+       continue;
+
+      if (grid_button == overlay.grid_button_highlight)
+       alpha_draw = alpha_highlight;
+
+      if (draw_pressed && overlay.grid_button_action & grid_button_action)
+      {
+       if (draw_outlined)
+         draw_outlined = FALSE;
+       else
+         alpha_draw = MIN((float)alpha_draw * 1.5, SDL_ALPHA_OPAQUE);
+      }
+
+      SDL_SetRenderDrawColor(sdl_renderer, 255, 255, 255, alpha_draw);
+
+      rect.x = (x + 0) * video.screen_width  / grid_xsize;
+      rect.y = (y + 0) * video.screen_height / grid_ysize;
+      rect.w = (x + 1) * video.screen_width  / grid_xsize - rect.x;
+      rect.h = (y + 1) * video.screen_height / grid_ysize - rect.y;
+
+      if (x == 0 || overlay.grid_button[x - 1][y] != grid_button)
+      {
+       rect.x += border_size;
+       rect.w -= border_size;
+
+       outline_border |= MV_LEFT;
+      }
+
+      if (x == grid_xsize - 1 || overlay.grid_button[x + 1][y] != grid_button)
+      {
+       rect.w -= border_size;
+
+       outline_border |= MV_RIGHT;
+      }
+
+      if (y == 0 || overlay.grid_button[x][y - 1] != grid_button)
+      {
+       rect.y += border_size;
+       rect.h -= border_size;
+
+       outline_border |= MV_UP;
+      }
+
+      if (y == grid_ysize - 1 || overlay.grid_button[x][y + 1] != grid_button)
+      {
+       rect.h -= border_size;
+
+       outline_border |= MV_DOWN;
+      }
+
+      if (draw_outlined)
+      {
+       int rect_x = rect.x +
+         (outline_border & MV_LEFT  ? border_size : 0);
+       int rect_w = rect.w -
+         (outline_border & MV_LEFT  ? border_size : 0) -
+         (outline_border & MV_RIGHT ? border_size : 0);
+
+       if (outline_border & MV_LEFT)
+         RenderFillRectangle(rect.x, rect.y, border_size, rect.h);
+
+       if (outline_border & MV_RIGHT)
+         RenderFillRectangle(rect.x + rect.w - border_size, rect.y,
+                             border_size, rect.h);
+
+       if (outline_border & MV_UP)
+         RenderFillRectangle(rect_x, rect.y, rect_w, border_size);
+
+       if (outline_border & MV_DOWN)
+         RenderFillRectangle(rect_x, rect.y + rect.h - border_size,
+                             rect_w, border_size);
+      }
+      else
+      {
+       SDL_RenderFillRect(sdl_renderer, &rect);
+      }
+    }
+  }
+
+  SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
+}
+
+static void DrawTouchInputOverlay(void)
 {
   static SDL_Texture *texture = NULL;
   static boolean initialized = FALSE;
   static boolean deactivated = TRUE;
+  static boolean show_grid = FALSE;
   static int width = 0, height = 0;
-  static int alpha_max = SDL_ALPHA_OPAQUE / 2;
-  static int alpha_step = 5;
-  static int alpha_last = 0;
+  static int alpha_last = -1;
   static int alpha = 0;
+  int alpha_max = ALPHA_FROM_TRANSPARENCY(setup.touch.transparency);
+  int alpha_step = ALPHA_FADING_STEPSIZE(alpha_max);
+  boolean active = (overlay.enabled && overlay.active);
 
-  if (!overlay.active && deactivated)
+  if (!active && deactivated)
     return;
 
-  if (overlay.active)
+  if (active)
   {
     if (alpha < alpha_max)
       alpha = MIN(alpha + alpha_step, alpha_max);
@@ -2950,6 +3210,21 @@ static void DrawTouchInputOverlay()
       deactivated = TRUE;
   }
 
+  if (overlay.show_grid)
+    show_grid = TRUE;
+  else if (deactivated)
+    show_grid = FALSE;
+
+  if (show_grid)
+    DrawTouchInputOverlay_ShowGrid(alpha);
+
+  DrawTouchInputOverlay_ShowGridButtons(alpha);
+
+  return;
+
+
+  // !!! VIRTUAL BUTTONS FROM IMAGE FILE NOT USED ANYMORE !!!
+
   if (!initialized)
   {
     char *basename = "overlay/VirtualButtons.png";
@@ -2978,7 +3253,6 @@ static void DrawTouchInputOverlay()
     SDL_FreeSurface(surface);
 
     SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
-    SDL_SetTextureAlphaMod(texture, alpha_max);
 
     initialized = TRUE;
   }