fixed assertion warning when building with SDL 2.0.6 for Android
[rocksndiamonds.git] / src / libgame / sdl.c
index 893f765351bf8c45a9a58e0cf3ddde11d61de68e..214859df78ef187e584ce4b42837b653a0f6e0f4 100644 (file)
@@ -37,6 +37,11 @@ static boolean limit_screen_updates = FALSE;
 /* functions from SGE library */
 void sge_Line(SDL_Surface *, Sint16, Sint16, Sint16, Sint16, Uint32);
 
+#if defined(USE_TOUCH_INPUT_OVERLAY)
+/* functions to draw overlay graphics for touch device input */
+static void DrawTouchInputOverlay();
+#endif
+
 void SDLLimitScreenUpdates(boolean enable)
 {
   limit_screen_updates = enable;
@@ -129,6 +134,56 @@ static void UpdateScreenExt(SDL_Rect *rect, boolean with_frame_delay)
     SDL_UpdateTexture(sdl_texture, NULL, screen->pixels, screen->pitch);
   }
 
+  int xoff = video.screen_xoffset;
+  int yoff = video.screen_yoffset;
+  SDL_Rect dst_rect_screen = { xoff, yoff, video.width, video.height };
+  SDL_Rect *src_rect1 = NULL, *dst_rect1 = NULL;
+  SDL_Rect *src_rect2 = NULL, *dst_rect2 = NULL;
+
+  if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
+      video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
+    dst_rect2 = &dst_rect_screen;
+  else
+    dst_rect1 = &dst_rect_screen;
+
+#if defined(HAS_SCREEN_KEYBOARD)
+  if (video.shifted_up || video.shifted_up_delay)
+  {
+    int time_current = SDL_GetTicks();
+    int pos = video.shifted_up_pos;
+    int pos_last = video.shifted_up_pos_last;
+
+    if (!DelayReachedExt(&video.shifted_up_delay, video.shifted_up_delay_value,
+                        time_current))
+    {
+      int delay = time_current - video.shifted_up_delay;
+      int delay_value = video.shifted_up_delay_value;
+
+      pos = pos_last + (pos - pos_last) * delay / delay_value;
+    }
+    else
+    {
+      video.shifted_up_pos_last = pos;
+      video.shifted_up_delay = 0;
+    }
+
+    SDL_Rect src_rect_up = { 0,    pos,  video.width, video.height - pos };
+    SDL_Rect dst_rect_up = { xoff, yoff, video.width, video.height - pos };
+
+    if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
+       video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
+    {
+      src_rect2 = &src_rect_up;
+      dst_rect2 = &dst_rect_up;
+    }
+    else
+    {
+      src_rect1 = &src_rect_up;
+      dst_rect1 = &dst_rect_up;
+    }
+  }
+#endif
+
   // clear render target buffer
   SDL_RenderClear(sdl_renderer);
 
@@ -139,7 +194,7 @@ static void UpdateScreenExt(SDL_Rect *rect, boolean with_frame_delay)
 
   // copy backbuffer texture to render target buffer
   if (video.screen_rendering_mode != SPECIAL_RENDERING_TARGET)
-    SDL_RenderCopy(sdl_renderer, sdl_texture_stream, NULL, NULL);
+    SDL_RenderCopy(sdl_renderer, sdl_texture_stream, src_rect1, dst_rect1);
 
   if (video.screen_rendering_mode != SPECIAL_RENDERING_BITMAP)
     FinalizeScreen(DRAW_TO_SCREEN);
@@ -149,8 +204,14 @@ static void UpdateScreenExt(SDL_Rect *rect, boolean with_frame_delay)
       video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
   {
     SDL_SetRenderTarget(sdl_renderer, NULL);
-    SDL_RenderCopy(sdl_renderer, sdl_texture_target, NULL, NULL);
+    SDL_RenderCopy(sdl_renderer, sdl_texture_target, src_rect2, dst_rect2);
   }
+
+#if defined(USE_TOUCH_INPUT_OVERLAY)
+  // draw overlay graphics for touch device input, if needed
+  DrawTouchInputOverlay();
+#endif
+
 #endif
 
   // global synchronization point of the game to align video frame delay
@@ -340,10 +401,12 @@ SDL_Surface *SDLGetNativeSurface(SDL_Surface *surface)
   if (surface == NULL)
     return NULL;
 
-  if (video.initialized)
-    new_surface = SDL_DisplayFormat(surface);
-  else
+  if (!video.initialized)
     new_surface = SDL_ConvertSurface(surface, surface->format, SURFACE_FLAGS);
+  else if (SDLHasAlpha(surface))
+    new_surface = SDL_DisplayFormatAlpha(surface);
+  else
+    new_surface = SDL_DisplayFormat(surface);
 
   if (new_surface == NULL)
     Error(ERR_EXIT, "%s() failed: %s",
@@ -376,6 +439,9 @@ boolean SDLSetNativeSurface(SDL_Surface **surface)
 #if defined(TARGET_SDL2)
 static SDL_Texture *SDLCreateTextureFromSurface(SDL_Surface *surface)
 {
+  if (program.headless)
+    return NULL;
+
   SDL_Texture *texture = SDL_CreateTextureFromSurface(sdl_renderer, surface);
 
   if (texture == NULL)
@@ -439,8 +505,11 @@ void SDLInitVideoDisplay(void)
 #endif
 }
 
-void SDLInitVideoBuffer(boolean fullscreen)
+inline static void SDLInitVideoBuffer_VideoBuffer(boolean fullscreen)
 {
+  if (program.headless)
+    return;
+
   video.window_scaling_percent = setup.window_scaling_percent;
   video.window_scaling_quality = setup.window_scaling_quality;
 
@@ -463,12 +532,11 @@ void SDLInitVideoBuffer(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()
+{
   /* SDL cannot directly draw to the visible video framebuffer like X11,
      but always uses a backbuffer, which is then blitted to the visible
      video framebuffer with 'SDL_UpdateRect' (or replaced with the current
@@ -484,6 +552,16 @@ void SDLInitVideoBuffer(boolean fullscreen)
 
   /* create additional (symbolic) buffer for double-buffering */
   ReCreateBitmap(&window, video.width, video.height);
+
+  /* create dummy drawing buffer for headless mode, if needed */
+  if (program.headless)
+    ReCreateBitmap(&backbuffer, video.width, video.height);
+}
+
+void SDLInitVideoBuffer(boolean fullscreen)
+{
+  SDLInitVideoBuffer_VideoBuffer(fullscreen);
+  SDLInitVideoBuffer_DrawBuffer();
 }
 
 static boolean SDLCreateScreen(boolean fullscreen)
@@ -508,16 +586,20 @@ static boolean SDLCreateScreen(boolean fullscreen)
      it will try to use accelerated graphics and apparently fails miserably) */
   int renderer_flags = SDL_RENDERER_SOFTWARE;
 #endif
+
+  SDLSetScreenSizeAndOffsets(video.width, video.height);
 #endif
 
   int width  = video.width;
   int height = video.height;
+  int screen_width  = video.screen_width;
+  int screen_height = video.screen_height;
   int surface_flags = (fullscreen ? surface_flags_fullscreen :
                       surface_flags_window);
 
   // default window size is unscaled
-  video.window_width  = video.width;
-  video.window_height = video.height;
+  video.window_width  = screen_width;
+  video.window_height = screen_height;
 
 #if defined(TARGET_SDL2)
 
@@ -526,8 +608,8 @@ static boolean SDLCreateScreen(boolean fullscreen)
 
   float window_scaling_factor = (float)setup.window_scaling_percent / 100;
 
-  video.window_width  = window_scaling_factor * width;
-  video.window_height = window_scaling_factor * height;
+  video.window_width  = window_scaling_factor * screen_width;
+  video.window_height = window_scaling_factor * screen_height;
 
   if (sdl_texture_stream)
   {
@@ -571,7 +653,7 @@ static boolean SDLCreateScreen(boolean fullscreen)
 
     if (sdl_renderer != NULL)
     {
-      SDL_RenderSetLogicalSize(sdl_renderer, width, height);
+      SDL_RenderSetLogicalSize(sdl_renderer, screen_width, screen_height);
       // SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
       SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, setup.window_scaling_quality);
 
@@ -744,6 +826,9 @@ boolean SDLSetVideoMode(boolean fullscreen)
 void SDLSetWindowTitle()
 {
 #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);
@@ -757,8 +842,8 @@ void SDLSetWindowScaling(int window_scaling_percent)
     return;
 
   float window_scaling_factor = (float)window_scaling_percent / 100;
-  int new_window_width  = (int)(window_scaling_factor * video.width);
-  int new_window_height = (int)(window_scaling_factor * video.height);
+  int new_window_width  = (int)(window_scaling_factor * video.screen_width);
+  int new_window_height = (int)(window_scaling_factor * video.screen_height);
 
   SDL_SetWindowSize(sdl_window, new_window_width, new_window_height);
 
@@ -830,6 +915,67 @@ void SDLSetWindowFullscreen(boolean fullscreen)
     video.fullscreen_initial = FALSE;
   }
 }
+
+void SDLSetDisplaySize()
+{
+  SDL_Rect display_bounds;
+
+  SDL_GetDisplayBounds(0, &display_bounds);
+
+  video.display_width  = display_bounds.w;
+  video.display_height = display_bounds.h;
+
+#if 0
+  Error(ERR_DEBUG, "SDL real screen size: %d x %d",
+       video.display_width, video.display_height);
+#endif
+}
+
+void SDLSetScreenSizeAndOffsets(int width, int height)
+{
+  // set default video screen size and offsets
+  video.screen_width = width;
+  video.screen_height = height;
+  video.screen_xoffset = 0;
+  video.screen_yoffset = 0;
+
+#if defined(USE_COMPLETE_DISPLAY)
+  float ratio_video   = (float) width / height;
+  float ratio_display = (float) video.display_width / video.display_height;
+
+  if (ratio_video != ratio_display)
+  {
+    // adjust drawable screen size to cover the whole device display
+
+    if (ratio_video < ratio_display)
+      video.screen_width  *= ratio_display / ratio_video;
+    else
+      video.screen_height *= ratio_video / ratio_display;
+
+    video.screen_xoffset = (video.screen_width  - width)  / 2;
+    video.screen_yoffset = (video.screen_height - height) / 2;
+
+#if 0
+    Error(ERR_DEBUG, "Changing screen from %dx%d to %dx%d (%.2f to %.2f)",
+         width, height,
+         video.screen_width, video.screen_height,
+         ratio_video, ratio_display);
+#endif
+  }
+#endif
+}
+
+void SDLSetScreenSizeForRenderer(int width, int height)
+{
+  SDL_RenderSetLogicalSize(sdl_renderer, width, height);
+}
+
+void SDLSetScreenProperties()
+{
+  SDLSetScreenSizeAndOffsets(video.width, video.height);
+  SDLSetScreenSizeForRenderer(video.screen_width, video.screen_height);
+}
+
 #endif
 
 void SDLSetScreenRenderingMode(char *screen_rendering_mode)
@@ -855,6 +1001,9 @@ void SDLRedrawWindow()
 void SDLCreateBitmapContent(Bitmap *bitmap, int width, int height,
                            int depth)
 {
+  if (program.headless)
+    return;
+
   SDL_Surface *surface =
     SDL_CreateRGBSurface(SURFACE_FLAGS, width, height, depth, 0,0,0, 0);
 
@@ -1044,11 +1193,8 @@ void SDLFadeRectangle(int x, int y, int width, int height,
     int i;
 
     SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
-#if defined(TARGET_SDL2)
-    SDL_SetSurfaceBlendMode(surface_target, SDL_BLENDMODE_NONE);
-#else
-    SDL_SetAlpha(surface_target, 0, 0);                /* disable alpha blending */
-#endif
+
+    SDLSetAlpha(surface_target, FALSE, 0);     /* disable alpha blending */
 
     ypos[0] = -GetSimpleRandom(16);
 
@@ -1161,11 +1307,8 @@ void SDLFadeRectangle(int x, int y, int width, int height,
     int xx_size = width / 2;
 
     SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
-#if defined(TARGET_SDL2)
-    SDL_SetSurfaceBlendMode(surface_source, SDL_BLENDMODE_NONE);
-#else
-    SDL_SetAlpha(surface_source, 0, 0);                /* disable alpha blending */
-#endif
+
+    SDLSetAlpha(surface_source, FALSE, 0);     /* disable alpha blending */
 
     for (xx = 0; xx < xx_size;)
     {
@@ -1228,12 +1371,7 @@ void SDLFadeRectangle(int x, int y, int width, int height,
       SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
 
       /* draw new (target) image to screen buffer using alpha blending */
-#if defined(TARGET_SDL2)
-      SDL_SetSurfaceAlphaMod(surface_target, alpha_final);
-      SDL_SetSurfaceBlendMode(surface_target, SDL_BLENDMODE_BLEND);
-#else
-      SDL_SetAlpha(surface_target, SDL_SRCALPHA, alpha_final);
-#endif
+      SDLSetAlpha(surface_target, TRUE, alpha_final);
       SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
 
       if (draw_border_function != NULL)
@@ -2133,7 +2271,8 @@ SDL_Surface *zoomSurface(SDL_Surface *src, int dst_width, int dst_height)
   {
     /* new source surface is 32 bit with a defined RGB ordering */
     zoom_src = SDL_CreateRGBSurface(SURFACE_FLAGS, src->w, src->h, 32,
-                                   0x000000ff, 0x0000ff00, 0x00ff0000, 0);
+                                   0x000000ff, 0x0000ff00, 0x00ff0000,
+                                   (src->format->Amask ? 0xff000000 : 0));
     SDL_BlitSurface(src, NULL, zoom_src, NULL);
     is_32bit = TRUE;
     is_converted = TRUE;
@@ -2146,7 +2285,8 @@ SDL_Surface *zoomSurface(SDL_Surface *src, int dst_width, int dst_height)
     zoom_dst = SDL_CreateRGBSurface(SURFACE_FLAGS, dst_width, dst_height, 32,
                                    zoom_src->format->Rmask,
                                    zoom_src->format->Gmask,
-                                   zoom_src->format->Bmask, 0);
+                                   zoom_src->format->Bmask,
+                                   zoom_src->format->Amask);
   }
   else
   {
@@ -2187,10 +2327,30 @@ SDL_Surface *zoomSurface(SDL_Surface *src, int dst_width, int dst_height)
   return zoom_dst;
 }
 
+static SDL_Surface *SDLGetOpaqueSurface(SDL_Surface *surface)
+{
+  SDL_Surface *new_surface;
+
+  if (surface == NULL)
+    return NULL;
+
+  if ((new_surface = SDLGetNativeSurface(surface)) == NULL)
+    Error(ERR_EXIT, "SDLGetNativeSurface() failed");
+
+  /* remove alpha channel from native non-transparent surface, if defined */
+  SDLSetAlpha(new_surface, FALSE, 0);
+
+  /* remove transparent color from native non-transparent surface, if defined */
+  SDL_SetColorKey(new_surface, UNSET_TRANSPARENT_PIXEL, 0);
+
+  return new_surface;
+}
+
 Bitmap *SDLZoomBitmap(Bitmap *src_bitmap, int dst_width, int dst_height)
 {
   Bitmap *dst_bitmap = CreateBitmapStruct();
-  SDL_Surface **dst_surface = &dst_bitmap->surface;
+  SDL_Surface *src_surface = src_bitmap->surface_masked;
+  SDL_Surface *dst_surface;
 
   dst_width  = MAX(1, dst_width);      /* prevent zero bitmap width */
   dst_height = MAX(1, dst_height);     /* prevent zero bitmap height */
@@ -2199,10 +2359,21 @@ Bitmap *SDLZoomBitmap(Bitmap *src_bitmap, int dst_width, int dst_height)
   dst_bitmap->height = dst_height;
 
   /* create zoomed temporary surface from source surface */
-  *dst_surface = zoomSurface(src_bitmap->surface, dst_width, dst_height);
+  dst_surface = zoomSurface(src_surface, dst_width, dst_height);
 
   /* create native format destination surface from zoomed temporary surface */
-  SDLSetNativeSurface(dst_surface);
+  SDLSetNativeSurface(&dst_surface);
+
+  /* set color key for zoomed surface from source surface, if defined */
+  if (SDLHasColorKey(src_surface))
+    SDL_SetColorKey(dst_surface, SET_TRANSPARENT_PIXEL,
+                   SDLGetColorKey(src_surface));
+
+  /* create native non-transparent surface for opaque blitting */
+  dst_bitmap->surface = SDLGetOpaqueSurface(dst_surface);
+
+  /* set native transparent surface for masked blitting */
+  dst_bitmap->surface_masked = dst_surface;
 
   return dst_bitmap;
 }
@@ -2217,47 +2388,45 @@ Bitmap *SDLLoadImage(char *filename)
   Bitmap *new_bitmap = CreateBitmapStruct();
   SDL_Surface *sdl_image_tmp;
 
+  if (program.headless)
+  {
+    /* prevent sanity check warnings at later stage */
+    new_bitmap->width = new_bitmap->height = 1;
+
+    return new_bitmap;
+  }
+
   print_timestamp_init("SDLLoadImage");
 
   print_timestamp_time(getBaseNamePtr(filename));
 
   /* load image to temporary surface */
   if ((sdl_image_tmp = IMG_Load(filename)) == NULL)
-  {
-    SetError("IMG_Load(): %s", SDL_GetError());
-
-    return NULL;
-  }
+    Error(ERR_EXIT, "IMG_Load() failed: %s", SDL_GetError());
 
   print_timestamp_time("IMG_Load");
 
   UPDATE_BUSY_STATE();
 
   /* create native non-transparent surface for current image */
-  if ((new_bitmap->surface = SDLGetNativeSurface(sdl_image_tmp)) == NULL)
-  {
-    SetError("SDL_DisplayFormat(): %s", SDL_GetError());
+  if ((new_bitmap->surface = SDLGetOpaqueSurface(sdl_image_tmp)) == NULL)
+    Error(ERR_EXIT, "SDLGetOpaqueSurface() failed");
 
-    return NULL;
-  }
-
-  print_timestamp_time("SDL_DisplayFormat (opaque)");
+  print_timestamp_time("SDLGetNativeSurface (opaque)");
 
   UPDATE_BUSY_STATE();
 
-  /* create native transparent surface for current image */
-  if (sdl_image_tmp->format->Amask == 0)
+  /* set black pixel to transparent if no alpha channel / transparent color */
+  if (!SDLHasAlpha(sdl_image_tmp) &&
+      !SDLHasColorKey(sdl_image_tmp))
     SDL_SetColorKey(sdl_image_tmp, SET_TRANSPARENT_PIXEL,
                    SDL_MapRGB(sdl_image_tmp->format, 0x00, 0x00, 0x00));
 
+  /* create native transparent surface for current image */
   if ((new_bitmap->surface_masked = SDLGetNativeSurface(sdl_image_tmp)) == NULL)
-  {
-    SetError("SDL_DisplayFormat(): %s", SDL_GetError());
+    Error(ERR_EXIT, "SDLGetNativeSurface() failed");
 
-    return NULL;
-  }
-
-  print_timestamp_time("SDL_DisplayFormat (masked)");
+  print_timestamp_time("SDLGetNativeSurface (masked)");
 
   UPDATE_BUSY_STATE();
 
@@ -2316,6 +2485,9 @@ void SDLSetMouseCursor(struct MouseCursorInfo *cursor_info)
 
 void SDLOpenAudio(void)
 {
+  if (program.headless)
+    return;
+
 #if !defined(TARGET_SDL2)
   if (!strEqual(setup.system.sdl_audiodriver, ARG_DEFAULT))
     SDL_putenv(getStringCat2("SDL_AUDIODRIVER=", setup.system.sdl_audiodriver));
@@ -2418,31 +2590,83 @@ void SDLHandleWindowManagerEvent(Event *event)
 /* joystick functions                                                        */
 /* ========================================================================= */
 
-static SDL_Joystick *sdl_joystick[MAX_PLAYERS] = { NULL, NULL, NULL, NULL };
-static int sdl_js_axis[MAX_PLAYERS][2]   = { {0, 0}, {0, 0}, {0, 0}, {0, 0} };
-static int sdl_js_button[MAX_PLAYERS][2] = { {0, 0}, {0, 0}, {0, 0}, {0, 0} };
+#if defined(TARGET_SDL2)
+static void *sdl_joystick[MAX_PLAYERS];                // game controller or joystick
+#else
+static SDL_Joystick *sdl_joystick[MAX_PLAYERS];        // only joysticks supported
+#endif
+static int sdl_js_axis_raw[MAX_PLAYERS][2];
+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()
 {
-  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;
 
-  return ((sdl_joystick[nr] = SDL_JoystickOpen(nr)) == NULL ? FALSE : TRUE);
+#if defined(TARGET_SDL2)
+  sdl_is_controller[nr] = SDL_IsGameController(nr);
+#else
+  sdl_is_controller[nr] = FALSE;
+#endif
+
+#if 1
+  Error(ERR_DEBUG, "opening joystick %d (%s)",
+       nr, (sdl_is_controller[nr] ? "game controller" : "joystick"));
+#endif
+
+#if defined(TARGET_SDL2)
+  if (sdl_is_controller[nr])
+    sdl_joystick[nr] = SDL_GameControllerOpen(nr);
+  else
+    sdl_joystick[nr] = SDL_JoystickOpen(nr);
+#else
+  sdl_joystick[nr] = SDL_JoystickOpen(nr);
+#endif
+
+  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
+  Error(ERR_DEBUG, "closing joystick %d", nr);
+#endif
+
+#if defined(TARGET_SDL2)
+  if (sdl_is_controller[nr])
+    SDL_GameControllerClose(sdl_joystick[nr]);
+  else
+    SDL_JoystickClose(sdl_joystick[nr]);
+#else
   SDL_JoystickClose(sdl_joystick[nr]);
+#endif
 
   sdl_joystick[nr] = NULL;
 }
 
-static boolean SDLCheckJoystickOpened(int nr)
+boolean SDLCheckJoystickOpened(int nr)
 {
-  if (nr < 0 || nr > MAX_PLAYERS)
+  if (nr < 0 || nr >= MAX_PLAYERS)
     return FALSE;
 
 #if defined(TARGET_SDL2)
@@ -2452,23 +2676,164 @@ static boolean SDLCheckJoystickOpened(int nr)
 #endif
 }
 
+static void setJoystickAxis(int nr, int axis_id_raw, int axis_value)
+{
+#if defined(TARGET_SDL2)
+  int axis_id = (axis_id_raw == SDL_CONTROLLER_AXIS_LEFTX ||
+                axis_id_raw == SDL_CONTROLLER_AXIS_RIGHTX ? 0 :
+                axis_id_raw == SDL_CONTROLLER_AXIS_LEFTY ||
+                axis_id_raw == SDL_CONTROLLER_AXIS_RIGHTY ? 1 : -1);
+#else
+  int axis_id = axis_id_raw % 2;
+#endif
+
+  if (nr < 0 || nr >= MAX_PLAYERS)
+    return;
+
+  if (axis_id == -1)
+    return;
+
+  // prevent (slightly jittering, but centered) axis A from resetting axis B
+  if (ABS(axis_value) < JOYSTICK_PERCENT * JOYSTICK_MAX_AXIS_POS / 100 &&
+      axis_id_raw != sdl_js_axis_raw[nr][axis_id])
+    return;
+
+  sdl_js_axis[nr][axis_id] = axis_value;
+  sdl_js_axis_raw[nr][axis_id] = axis_id_raw;
+}
+
+static void setJoystickButton(int nr, int button_id_raw, int button_state)
+{
+#if defined(TARGET_SDL2)
+  int button_id = (button_id_raw == SDL_CONTROLLER_BUTTON_A ||
+                  button_id_raw == SDL_CONTROLLER_BUTTON_X ||
+                  button_id_raw == SDL_CONTROLLER_BUTTON_LEFTSHOULDER ||
+                  button_id_raw == SDL_CONTROLLER_BUTTON_LEFTSTICK ||
+                  button_id_raw == SDL_CONTROLLER_BUTTON_RIGHTSTICK ? 0 :
+                  button_id_raw == SDL_CONTROLLER_BUTTON_B ||
+                  button_id_raw == SDL_CONTROLLER_BUTTON_Y ||
+                  button_id_raw == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER ? 1 :
+                  -1);
+
+  if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_LEFT)
+    sdl_js_axis[nr][0] = button_state * JOYSTICK_XLEFT;
+  else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
+    sdl_js_axis[nr][0] = button_state * JOYSTICK_XRIGHT;
+  else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_UP)
+    sdl_js_axis[nr][1] = button_state * JOYSTICK_YUPPER;
+  else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+    sdl_js_axis[nr][1] = button_state * JOYSTICK_YLOWER;
+
+  if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_LEFT ||
+      button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_RIGHT ||
+      button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_UP ||
+      button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+    sdl_js_axis_raw[nr][0] = sdl_js_axis_raw[nr][1] = -1;
+#else
+  int button_id = button_id_raw % 2;
+#endif
+
+  if (nr < 0 || nr >= MAX_PLAYERS)
+    return;
+
+  if (button_id == -1)
+    return;
+
+  sdl_js_button[nr][button_id] = button_state;
+}
+
 void HandleJoystickEvent(Event *event)
 {
   switch(event->type)
   {
+#if defined(TARGET_SDL2)
+    case SDL_CONTROLLERDEVICEADDED:
+#if 1
+      Error(ERR_DEBUG, "SDL_CONTROLLERDEVICEADDED: device %d added",
+           event->cdevice.which);
+#endif
+      InitJoysticks();
+      break;
+
+    case SDL_CONTROLLERDEVICEREMOVED:
+#if 1
+      Error(ERR_DEBUG, "SDL_CONTROLLERDEVICEREMOVED: device %d removed",
+           event->cdevice.which);
+#endif
+      InitJoysticks();
+      break;
+
+    case SDL_CONTROLLERAXISMOTION:
+#if 1
+      Error(ERR_DEBUG, "SDL_CONTROLLERAXISMOTION: device %d, axis %d: %d",
+           event->caxis.which, event->caxis.axis, event->caxis.value);
+#endif
+      setJoystickAxis(event->caxis.which,
+                     event->caxis.axis,
+                     event->caxis.value);
+      break;
+
+    case SDL_CONTROLLERBUTTONDOWN:
+#if 1
+      Error(ERR_DEBUG, "SDL_CONTROLLERBUTTONDOWN: device %d, button %d",
+           event->cbutton.which, event->cbutton.button);
+#endif
+      setJoystickButton(event->cbutton.which,
+                       event->cbutton.button,
+                       TRUE);
+      break;
+
+    case SDL_CONTROLLERBUTTONUP:
+#if 1
+      Error(ERR_DEBUG, "SDL_CONTROLLERBUTTONUP: device %d, button %d",
+           event->cbutton.which, event->cbutton.button);
+#endif
+      setJoystickButton(event->cbutton.which,
+                       event->cbutton.button,
+                       FALSE);
+      break;
+#endif
+
     case SDL_JOYAXISMOTION:
-      if (event->jaxis.axis < 2)
-       sdl_js_axis[event->jaxis.which][event->jaxis.axis]= event->jaxis.value;
+      if (sdl_is_controller[event->jaxis.which])
+       break;
+
+#if 1
+      Error(ERR_DEBUG, "SDL_JOYAXISMOTION: device %d, axis %d: %d",
+           event->jaxis.which, event->jaxis.axis, event->jaxis.value);
+#endif
+      if (event->jaxis.axis < 4)
+       setJoystickAxis(event->jaxis.which,
+                       event->jaxis.axis,
+                       event->jaxis.value);
       break;
 
     case SDL_JOYBUTTONDOWN:
-      if (event->jbutton.button < 2)
-       sdl_js_button[event->jbutton.which][event->jbutton.button] = TRUE;
+      if (sdl_is_controller[event->jaxis.which])
+       break;
+
+#if 1
+      Error(ERR_DEBUG, "SDL_JOYBUTTONDOWN: device %d, button %d",
+           event->jbutton.which, event->jbutton.button);
+#endif
+      if (event->jbutton.button < 4)
+       setJoystickButton(event->jbutton.which,
+                         event->jbutton.button,
+                         TRUE);
       break;
 
     case SDL_JOYBUTTONUP:
-      if (event->jbutton.button < 2)
-       sdl_js_button[event->jbutton.which][event->jbutton.button] = FALSE;
+      if (sdl_is_controller[event->jaxis.which])
+       break;
+
+#if 1
+      Error(ERR_DEBUG, "SDL_JOYBUTTONUP: device %d, button %d",
+           event->jbutton.which, event->jbutton.button);
+#endif
+      if (event->jbutton.button < 4)
+       setJoystickButton(event->jbutton.which,
+                         event->jbutton.button,
+                         FALSE);
       break;
 
     default:
@@ -2480,19 +2845,73 @@ void SDLInitJoysticks()
 {
   static boolean sdl_joystick_subsystem_initialized = FALSE;
   boolean print_warning = !sdl_joystick_subsystem_initialized;
+#if defined(TARGET_SDL2)
+  char *mappings_file_base = getPath2(options.conf_directory,
+                                     GAMECONTROLLER_BASENAME);
+  char *mappings_file_user = getPath2(getUserGameDataDir(),
+                                     GAMECONTROLLER_BASENAME);
+  int num_mappings;
+#endif
   int i;
 
   if (!sdl_joystick_subsystem_initialized)
   {
     sdl_joystick_subsystem_initialized = TRUE;
 
+#if defined(TARGET_SDL2)
+    SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
+
+    if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0)
+#else
     if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0)
+#endif
     {
       Error(ERR_EXIT, "SDL_Init() failed: %s", SDL_GetError());
       return;
     }
+
+#if defined(TARGET_SDL2)
+    num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_base);
+
+    if (num_mappings != -1)
+      Error(ERR_INFO, "%d game controller base mapping(s) added", num_mappings);
+    else
+      Error(ERR_WARN, "no game controller base mappings found");
+
+    num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_user);
+
+    if (num_mappings != -1)
+      Error(ERR_INFO, "%d game controller user mapping(s) added", num_mappings);
+    else
+      Error(ERR_WARN, "no game controller user mappings found");
+
+    Error(ERR_INFO, "%d joystick(s) found:", SDL_NumJoysticks());
+
+    checked_free(mappings_file_base);
+    checked_free(mappings_file_user);
+
+    for (i = 0; i < SDL_NumJoysticks(); i++)
+    {
+      const char *name, *type;
+
+      if (SDL_IsGameController(i))
+      {
+       name = SDL_GameControllerNameForIndex(i);
+       type = "game controller";
+      }
+      else
+      {
+       name = SDL_JoystickNameForIndex(i);
+       type = "joystick";
+      }
+
+      Error(ERR_INFO, "- joystick %d (%s): '%s'",
+           i, type, (name ? name : "(Unknown)"));
+    }
+#endif
   }
 
+  /* assign joysticks from configured to connected joystick for all players */
   for (i = 0; i < MAX_PLAYERS; i++)
   {
     /* get configured joystick for this player */
@@ -2507,29 +2926,24 @@ void SDLInitJoysticks()
       joystick_nr = -1;
     }
 
-    /* misuse joystick file descriptor variable to store joystick number */
-    joystick.fd[i] = joystick_nr;
-
-    if (joystick_nr == -1)
-      continue;
+    /* store configured joystick number for each player */
+    joystick.nr[i] = joystick_nr;
+  }
 
+  /* now open all connected joysticks (regardless if configured or not) */
+  for (i = 0; i < SDL_NumJoysticks(); i++)
+  {
     /* this allows subsequent calls to 'InitJoysticks' for re-initialization */
-    if (SDLCheckJoystickOpened(joystick_nr))
-      SDLCloseJoystick(joystick_nr);
+    if (SDLCheckJoystickOpened(i))
+      SDLCloseJoystick(i);
 
-    if (!setup.input[i].use_joystick)
-      continue;
-
-    if (!SDLOpenJoystick(joystick_nr))
-    {
-      if (print_warning)
-       Error(ERR_WARN, "cannot open joystick %d", joystick_nr);
-
-      continue;
-    }
-
-    joystick.status = JOYSTICK_ACTIVATED;
+    if (SDLOpenJoystick(i))
+      joystick.status = JOYSTICK_ACTIVATED;
+    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)
@@ -2549,3 +2963,104 @@ 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 SDL_Texture *texture = NULL;
+  static boolean initialized = FALSE;
+  static boolean deactivated = TRUE;
+  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 = 0;
+  boolean active = (overlay.enabled && overlay.active);
+
+  if (!active && deactivated)
+    return;
+
+  if (active)
+  {
+    if (alpha < alpha_max)
+      alpha = MIN(alpha + alpha_step, alpha_max);
+
+    deactivated = FALSE;
+  }
+  else
+  {
+    alpha = MAX(0, alpha - alpha_step);
+
+    if (alpha == 0)
+      deactivated = TRUE;
+  }
+
+  if (!initialized)
+  {
+    char *basename = "overlay/VirtualButtons.png";
+    char *filename = getCustomImageFilename(basename);
+
+    if (filename == NULL)
+      Error(ERR_EXIT, "LoadCustomImage(): cannot find file '%s'", basename);
+
+    SDL_Surface *surface;
+
+    if ((surface = IMG_Load(filename)) == NULL)
+      Error(ERR_EXIT, "IMG_Load() failed: %s", SDL_GetError());
+
+    width  = surface->w;
+    height = surface->h;
+
+    /* set black pixel to transparent if no alpha channel / transparent color */
+    if (!SDLHasAlpha(surface) &&
+       !SDLHasColorKey(surface))
+      SDL_SetColorKey(surface, SET_TRANSPARENT_PIXEL,
+                     SDL_MapRGB(surface->format, 0x00, 0x00, 0x00));
+
+    if ((texture = SDLCreateTextureFromSurface(surface)) == NULL)
+      Error(ERR_EXIT, "SDLCreateTextureFromSurface() failed");
+
+    SDL_FreeSurface(surface);
+
+    SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
+    SDL_SetTextureAlphaMod(texture, alpha_max);
+
+    initialized = TRUE;
+  }
+
+  if (alpha != alpha_last)
+    SDL_SetTextureAlphaMod(texture, alpha);
+
+  alpha_last = alpha;
+
+  float ratio_overlay = (float) width / height;
+  float ratio_screen = (float) video.screen_width / video.screen_height;
+  int width_scaled, height_scaled;
+  int xpos, ypos;
+
+  if (ratio_overlay > ratio_screen)
+  {
+    width_scaled = video.screen_width;
+    height_scaled = video.screen_height * ratio_screen / ratio_overlay;
+    xpos = 0;
+    ypos = video.screen_height - height_scaled;
+  }
+  else
+  {
+    width_scaled = video.screen_width * ratio_overlay / ratio_screen;
+    height_scaled = video.screen_height;
+    xpos = (video.screen_width - width_scaled) / 2;
+    ypos = 0;
+  }
+
+  SDL_Rect src_rect = { 0, 0, width, height };
+  SDL_Rect dst_rect = { xpos, ypos, width_scaled, height_scaled };
+
+  SDL_RenderCopy(sdl_renderer, texture, &src_rect, &dst_rect);
+}
+#endif