fixed compiler warning due to missing function prototype
[rocksndiamonds.git] / src / libgame / sdl.c
index 3b0ccc070b7cb72bf050471b454d3f271dbe03de..2c8e116acf4025ab75f06e323806050b746d9d04 100644 (file)
 #include "misc.h"
 #include "setup.h"
 
-#define ENABLE_UNUSED_CODE     0       /* currently unused functions */
+#define ENABLE_UNUSED_CODE     0       // currently unused functions
 
+#define DEBUG_JOYSTICKS                0
 
-/* ========================================================================= */
-/* video functions                                                           */
-/* ========================================================================= */
 
-/* SDL internal variables */
-#if defined(TARGET_SDL2)
+// ============================================================================
+// video functions
+// ============================================================================
+
+// SDL internal variables
 static SDL_Window *sdl_window = NULL;
 static SDL_Renderer *sdl_renderer = NULL;
-static SDL_Texture *sdl_texture = NULL;
+static SDL_Texture *sdl_texture_stream = NULL;
+static SDL_Texture *sdl_texture_target = NULL;
 static boolean fullscreen_enabled = FALSE;
-
-#define USE_RENDERER   TRUE
-#endif
-
-/* stuff needed to work around SDL/Windows fullscreen drawing bug */
-static int fullscreen_width;
-static int fullscreen_height;
-static int fullscreen_xoffset;
-static int fullscreen_yoffset;
-static int video_xoffset;
-static int video_yoffset;
 static boolean limit_screen_updates = FALSE;
 
 
-/* functions from SGE library */
+// 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(void);
+#endif
+
 void SDLLimitScreenUpdates(boolean enable)
 {
   limit_screen_updates = enable;
 }
 
-static void UpdateScreen(SDL_Rect *rect)
+static void FinalizeScreen(int draw_target)
+{
+  // copy global animations to render target buffer, if defined (below border)
+  if (gfx.draw_global_anim_function != NULL)
+    gfx.draw_global_anim_function(draw_target, DRAW_GLOBAL_ANIM_STAGE_1);
+
+  // copy global masked border to render target buffer, if defined
+  if (gfx.draw_global_border_function != NULL)
+    gfx.draw_global_border_function(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)
 {
   static unsigned int update_screen_delay = 0;
-  unsigned int update_screen_delay_value = 20;         /* (milliseconds) */
+  unsigned int update_screen_delay_value = 50;         // (milliseconds)
+  SDL_Surface *screen = backbuffer->surface;
 
   if (limit_screen_updates &&
       !DelayReached(&update_screen_delay, update_screen_delay_value))
@@ -78,18 +94,39 @@ static void UpdateScreen(SDL_Rect *rect)
   }
 #endif
 
-#if defined(TARGET_SDL2)
-#if USE_RENDERER
-  SDL_Surface *screen = backbuffer->surface;
+  if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP &&
+      gfx.final_screen_bitmap != NULL) // may not be initialized yet
+  {
+    // draw global animations using bitmaps instead of using textures
+    // to prevent texture scaling artefacts (this is potentially slower)
+
+    BlitBitmap(backbuffer, gfx.final_screen_bitmap, 0, 0,
+              gfx.win_xsize, gfx.win_ysize, 0, 0);
+
+    FinalizeScreen(DRAW_TO_SCREEN);
+
+    screen = gfx.final_screen_bitmap->surface;
+
+    // force full window redraw
+    rect = NULL;
+  }
+
+  SDL_Texture *sdl_texture = sdl_texture_stream;
+
+  // deactivate use of target texture if render targets are not supported
+  if ((video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
+       video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE) &&
+      sdl_texture_target == NULL)
+    video.screen_rendering_mode = SPECIAL_RENDERING_OFF;
+
+  if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET)
+    sdl_texture = sdl_texture_target;
 
   if (rect)
   {
     int bytes_x = screen->pitch / video.width;
     int bytes_y = screen->pitch;
 
-    if (video.fullscreen_enabled)
-      bytes_x = screen->pitch / fullscreen_width;
-
     SDL_UpdateTexture(sdl_texture, rect,
                      screen->pixels + rect->x * bytes_x + rect->y * bytes_y,
                      screen->pitch);
@@ -98,63 +135,121 @@ static void UpdateScreen(SDL_Rect *rect)
   {
     SDL_UpdateTexture(sdl_texture, NULL, screen->pixels, screen->pitch);
   }
-  SDL_RenderClear(sdl_renderer);
-  SDL_RenderCopy(sdl_renderer, sdl_texture, NULL, NULL);
-  SDL_RenderPresent(sdl_renderer);
-#else
-  if (rect)
-    SDL_UpdateWindowSurfaceRects(sdl_window, rect, 1);
+
+  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
-    SDL_UpdateWindowSurface(sdl_window);
+    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
 
-#else  // TARGET_SDL
-  if (rect)
-    SDL_UpdateRects(backbuffer->surface, 1, rect);
-  else
-    SDL_UpdateRect(backbuffer->surface, 0, 0, 0, 0);
+  // clear render target buffer
+  SDL_RenderClear(sdl_renderer);
+
+  // set renderer to use target texture for rendering
+  if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
+      video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
+    SDL_SetRenderTarget(sdl_renderer, sdl_texture_target);
+
+  // copy backbuffer texture to render target buffer
+  if (video.screen_rendering_mode != SPECIAL_RENDERING_TARGET)
+    SDL_RenderCopy(sdl_renderer, sdl_texture_stream, src_rect1, dst_rect1);
+
+  if (video.screen_rendering_mode != SPECIAL_RENDERING_BITMAP)
+    FinalizeScreen(DRAW_TO_SCREEN);
+
+  // when using target texture, copy it to screen buffer
+  if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
+      video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
+  {
+    SDL_SetRenderTarget(sdl_renderer, 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
-}
 
-static void setFullscreenParameters(char *fullscreen_mode_string)
-{
-#if defined(TARGET_SDL2)
-  fullscreen_width = video.width;
-  fullscreen_height = video.height;
-  fullscreen_xoffset = 0;
-  fullscreen_yoffset = 0;  
+  // global synchronization point of the game to align video frame delay
+  if (with_frame_delay)
+    WaitUntilDelayReached(&video.frame_delay, video.frame_delay_value);
 
-#else
+  // show render target buffer on screen
+  SDL_RenderPresent(sdl_renderer);
+}
 
-  struct ScreenModeInfo *fullscreen_mode;
-  int i;
+static void UpdateScreen_WithFrameDelay(SDL_Rect *rect)
+{
+  UpdateScreenExt(rect, TRUE);
+}
 
-  fullscreen_mode = get_screen_mode_from_string(fullscreen_mode_string);
+static void UpdateScreen_WithoutFrameDelay(SDL_Rect *rect)
+{
+  UpdateScreenExt(rect, FALSE);
+}
 
-  if (fullscreen_mode == NULL)
-    return;
+void Delay_WithScreenUpdates(unsigned int delay)
+{
+  unsigned int time_current = SDL_GetTicks();
+  unsigned int time_delayed = time_current + delay;
 
-  for (i = 0; video.fullscreen_modes[i].width != -1; i++)
+  while (time_current < time_delayed)
   {
-    if (fullscreen_mode->width  == video.fullscreen_modes[i].width &&
-       fullscreen_mode->height == video.fullscreen_modes[i].height)
-    {
-      fullscreen_width  = fullscreen_mode->width;
-      fullscreen_height = fullscreen_mode->height;
+    // updating the screen contains waiting for frame delay (non-busy)
+    UpdateScreen_WithFrameDelay(NULL);
 
-      fullscreen_xoffset = (fullscreen_width  - video.width)  / 2;
-      fullscreen_yoffset = (fullscreen_height - video.height) / 2;
-
-      break;
-    }
+    time_current = SDL_GetTicks();
   }
-#endif
 }
 
 static void SDLSetWindowIcon(char *basename)
 {
-  /* (setting the window icon on Mac OS X would replace the high-quality
-     dock icon with the currently smaller (and uglier) icon from file) */
+  // (setting the window icon on Mac OS X would replace the high-quality
+  // dock icon with the currently smaller (and uglier) icon from file)
 
 #if !defined(PLATFORM_MACOSX)
   char *filename = getCustomImageFilename(basename);
@@ -174,20 +269,14 @@ static void SDLSetWindowIcon(char *basename)
     return;
   }
 
-  /* set transparent color */
+  // set transparent color
   SDL_SetColorKey(surface, SET_TRANSPARENT_PIXEL,
                  SDL_MapRGB(surface->format, 0x00, 0x00, 0x00));
 
-#if defined(TARGET_SDL2)
   SDL_SetWindowIcon(sdl_window, surface);
-#else
-  SDL_WM_SetIcon(surface, NULL);
-#endif
 #endif
 }
 
-#if defined(TARGET_SDL2)
-
 static boolean equalSDLPixelFormat(SDL_PixelFormat *format1,
                                   SDL_PixelFormat *format2)
 {
@@ -196,47 +285,61 @@ static boolean equalSDLPixelFormat(SDL_PixelFormat *format1,
          format1->BytesPerPixel == format2->BytesPerPixel &&
          format1->Rmask         == format2->Rmask &&
          format1->Gmask         == format2->Gmask &&
-         format1->Bmask         == format2->Bmask &&
-         format1->Amask         == format2->Amask);
+         format1->Bmask         == format2->Bmask);
 }
 
-boolean SDLSetNativeSurface(SDL_Surface **surface)
+static Pixel SDLGetColorKey(SDL_Surface *surface)
 {
-  SDL_Surface *new_surface;
+  Pixel color_key;
 
-  if (surface == NULL ||
-      *surface == NULL ||
-      backbuffer == NULL ||
-      backbuffer->surface == NULL)
-    return FALSE;
+  if (SDL_GetColorKey(surface, &color_key) != 0)
+    return -1;
 
-  // if pixel format already optimized for destination surface, do nothing
-  if (equalSDLPixelFormat((*surface)->format, backbuffer->surface->format))
-    return FALSE;
+  return color_key;
+}
 
-  new_surface = SDL_ConvertSurface(*surface, backbuffer->surface->format, 0);
+static boolean SDLHasColorKey(SDL_Surface *surface)
+{
+  return (SDLGetColorKey(surface) != -1);
+}
 
-  if (new_surface == NULL)
-    Error(ERR_EXIT, "SDL_ConvertSurface() failed: %s", SDL_GetError());
+static boolean SDLHasAlpha(SDL_Surface *surface)
+{
+  SDL_BlendMode blend_mode;
 
-  SDL_FreeSurface(*surface);
+  if (SDL_GetSurfaceBlendMode(surface, &blend_mode) != 0)
+    return FALSE;
 
-  *surface = new_surface;
+  return (blend_mode == SDL_BLENDMODE_BLEND);
+}
 
-  return TRUE;
+static void SDLSetAlpha(SDL_Surface *surface, boolean set, int alpha)
+{
+  SDL_BlendMode blend_mode = (set ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE);
+
+  SDL_SetSurfaceBlendMode(surface, blend_mode);
+  SDL_SetSurfaceAlphaMod(surface, alpha);
 }
 
 SDL_Surface *SDLGetNativeSurface(SDL_Surface *surface)
 {
+  SDL_PixelFormat format;
   SDL_Surface *new_surface;
 
   if (surface == NULL)
     return NULL;
 
   if (backbuffer && backbuffer->surface)
-    new_surface = SDL_ConvertSurface(surface, backbuffer->surface->format, 0);
+  {
+    format = *backbuffer->surface->format;
+    format.Amask = surface->format->Amask;     // keep alpha channel
+  }
   else
-    new_surface = SDL_ConvertSurface(surface, surface->format, 0);
+  {
+    format = *surface->format;
+  }
+
+  new_surface = SDL_ConvertSurface(surface, &format, 0);
 
   if (new_surface == NULL)
     Error(ERR_EXIT, "SDL_ConvertSurface() failed: %s", SDL_GetError());
@@ -244,21 +347,21 @@ SDL_Surface *SDLGetNativeSurface(SDL_Surface *surface)
   return new_surface;
 }
 
-#else
-
 boolean SDLSetNativeSurface(SDL_Surface **surface)
 {
   SDL_Surface *new_surface;
 
   if (surface == NULL ||
       *surface == NULL ||
-      !video.initialized)
+      backbuffer == NULL ||
+      backbuffer->surface == NULL)
     return FALSE;
 
-  new_surface = SDL_DisplayFormat(*surface);
+  // if pixel format already optimized for destination surface, do nothing
+  if (equalSDLPixelFormat((*surface)->format, backbuffer->surface->format))
+    return FALSE;
 
-  if (new_surface == NULL)
-    Error(ERR_EXIT, "SDL_DisplayFormat() failed: %s", SDL_GetError());
+  new_surface = SDLGetNativeSurface(*surface);
 
   SDL_FreeSurface(*surface);
 
@@ -267,220 +370,88 @@ boolean SDLSetNativeSurface(SDL_Surface **surface)
   return TRUE;
 }
 
-SDL_Surface *SDLGetNativeSurface(SDL_Surface *surface)
+static SDL_Texture *SDLCreateTextureFromSurface(SDL_Surface *surface)
 {
-  SDL_Surface *new_surface;
+  if (program.headless)
+    return NULL;
 
-  if (video.initialized)
-    new_surface = SDL_DisplayFormat(surface);
-  else
-    new_surface = SDL_ConvertSurface(surface, surface->format, SURFACE_FLAGS);
+  SDL_Texture *texture = SDL_CreateTextureFromSurface(sdl_renderer, surface);
 
-  if (new_surface == NULL)
-    Error(ERR_EXIT, "%s() failed: %s",
-         (video.initialized ? "SDL_DisplayFormat" : "SDL_ConvertSurface"),
+  if (texture == NULL)
+    Error(ERR_EXIT, "SDL_CreateTextureFromSurface() failed: %s",
          SDL_GetError());
 
-  return new_surface;
+  return texture;
 }
 
-#endif
-
-void SDLInitVideoDisplay(void)
+void SDLCreateBitmapTextures(Bitmap *bitmap)
 {
-#if !defined(TARGET_SDL2)
-  if (!strEqual(setup.system.sdl_videodriver, ARG_DEFAULT))
-    SDL_putenv(getStringCat2("SDL_VIDEODRIVER=", setup.system.sdl_videodriver));
-
-  SDL_putenv("SDL_VIDEO_CENTERED=1");
-#endif
+  if (bitmap == NULL)
+    return;
 
-  /* initialize SDL video */
-  if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
-    Error(ERR_EXIT, "SDL_InitSubSystem() failed: %s", SDL_GetError());
+  if (bitmap->texture)
+    SDL_DestroyTexture(bitmap->texture);
+  if (bitmap->texture_masked)
+    SDL_DestroyTexture(bitmap->texture_masked);
 
-  /* set default SDL depth */
-#if !defined(TARGET_SDL2)
-  video.default_depth = SDL_GetVideoInfo()->vfmt->BitsPerPixel;
-#else
-  video.default_depth = 32;    // (how to determine video depth in SDL2?)
-#endif
+  bitmap->texture        = SDLCreateTextureFromSurface(bitmap->surface);
+  bitmap->texture_masked = SDLCreateTextureFromSurface(bitmap->surface_masked);
 }
 
-void SDLInitVideoBuffer(DrawBuffer **backbuffer, DrawWindow **window,
-                       boolean fullscreen)
+void SDLFreeBitmapTextures(Bitmap *bitmap)
 {
-#if !defined(TARGET_SDL2)
-  static int screen_xy[][2] =
-  {
-    {  640, 480 },
-    {  800, 600 },
-    { 1024, 768 },
-    {   -1,  -1 }
-  };
-#endif
-  SDL_Rect **modes = NULL;
-  boolean hardware_fullscreen_available = TRUE;
-  int i, j;
-
-  /* default: normal game window size */
-  fullscreen_width = video.width;
-  fullscreen_height = video.height;
-  fullscreen_xoffset = 0;
-  fullscreen_yoffset = 0;
+  if (bitmap == NULL)
+    return;
 
-#if !defined(TARGET_SDL2)
-  /* determine required standard fullscreen mode for game screen size */
-  for (i = 0; screen_xy[i][0] != -1; i++)
-  {
-    if (screen_xy[i][0] >= video.width && screen_xy[i][1] >= video.height)
-    {
-      fullscreen_width  = screen_xy[i][0];
-      fullscreen_height = screen_xy[i][1];
+  if (bitmap->texture)
+    SDL_DestroyTexture(bitmap->texture);
+  if (bitmap->texture_masked)
+    SDL_DestroyTexture(bitmap->texture_masked);
 
-      break;
-    }
-  }
+  bitmap->texture = NULL;
+  bitmap->texture_masked = NULL;
+}
 
-  fullscreen_xoffset = (fullscreen_width  - video.width)  / 2;
-  fullscreen_yoffset = (fullscreen_height - video.height) / 2;
-#endif
+void SDLInitVideoDisplay(void)
+{
+  // initialize SDL video
+  if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
+    Error(ERR_EXIT, "SDL_InitSubSystem() failed: %s", SDL_GetError());
 
-  checked_free(video.fullscreen_modes);
+  // set default SDL depth
+  video.default_depth = 32;    // (how to determine video depth in SDL2?)
+  //
+  // Code used with SDL 1.2:
+  // video.default_depth = SDL_GetVideoInfo()->vfmt->BitsPerPixel;
+}
 
-  video.fullscreen_modes = NULL;
-  video.fullscreen_mode_current = NULL;
+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;
 
-#if defined(TARGET_SDL2)
-  int num_displays = SDL_GetNumVideoDisplays();
-
-  if (num_displays > 0)
-  {
-    // currently only display modes of first display supported
-    int num_modes = SDL_GetNumDisplayModes(0);
-
-    if (num_modes > 0)
-    {
-      modes = checked_calloc((num_modes + 1) * sizeof(SDL_Rect *));
-
-      for (i = 0; i < num_modes; i++)
-      {
-       SDL_DisplayMode mode;
-
-       if (SDL_GetDisplayMode(0, i, &mode) < 0)
-         break;
-
-       modes[i] = checked_calloc(sizeof(SDL_Rect));
-
-       modes[i]->w = mode.w;
-       modes[i]->h = mode.h;
-      }
-    }
-  }
-#else
-  /* get available hardware supported fullscreen modes */
-  modes = SDL_ListModes(NULL, SDL_FULLSCREEN | SDL_HWSURFACE);
-#endif
-
-  if (modes == NULL)
-  {
-    /* no hardware screen modes available => no fullscreen mode support */
-    // video.fullscreen_available = FALSE;
-    hardware_fullscreen_available = FALSE;
-  }
-  else if (modes == (SDL_Rect **)-1)
-  {
-    /* fullscreen resolution is not restricted -- all resolutions available */
-    video.fullscreen_modes = checked_calloc(2 * sizeof(struct ScreenModeInfo));
-
-    /* use native video buffer size for fullscreen mode */
-    video.fullscreen_modes[0].width  = video.width;
-    video.fullscreen_modes[0].height = video.height;
-
-    video.fullscreen_modes[1].width  = -1;
-    video.fullscreen_modes[1].height = -1;
-  }
-  else
-  {
-    /* in this case, a certain number of screen modes is available */
-    int num_modes = 0;
-
-    for (i = 0; modes[i] != NULL; i++)
-    {
-      boolean found_mode = FALSE;
-
-      /* screen mode is smaller than video buffer size -- skip it */
-      if (modes[i]->w < video.width || modes[i]->h < video.height)
-       continue;
-
-      if (video.fullscreen_modes != NULL)
-       for (j = 0; video.fullscreen_modes[j].width != -1; j++)
-         if (modes[i]->w == video.fullscreen_modes[j].width &&
-             modes[i]->h == video.fullscreen_modes[j].height)
-           found_mode = TRUE;
-
-      if (found_mode)          /* screen mode already stored -- skip it */
-       continue;
-
-      /* new mode found; add it to list of available fullscreen modes */
-
-      num_modes++;
-
-      video.fullscreen_modes = checked_realloc(video.fullscreen_modes,
-                                              (num_modes + 1) *
-                                              sizeof(struct ScreenModeInfo));
-
-      video.fullscreen_modes[num_modes - 1].width  = modes[i]->w;
-      video.fullscreen_modes[num_modes - 1].height = modes[i]->h;
-
-      video.fullscreen_modes[num_modes].width  = -1;
-      video.fullscreen_modes[num_modes].height = -1;
-    }
-
-    if (num_modes == 0)
-    {
-      /* no appropriate screen modes available => no fullscreen mode support */
-      // video.fullscreen_available = FALSE;
-      hardware_fullscreen_available = FALSE;
-    }
-  }
-
-  video.fullscreen_available = hardware_fullscreen_available;
+  SDLSetScreenRenderingMode(setup.screen_rendering_mode);
 
-#if USE_DESKTOP_FULLSCREEN
-  // in SDL 2.0, there is always support for desktop fullscreen mode
-  // (in SDL 1.2, there is only support for "real" fullscreen mode)
+  // SDL 2.0: support for (desktop) fullscreen mode available
   video.fullscreen_available = TRUE;
-#endif
-
-#if defined(TARGET_SDL2)
-  if (modes)
-  {
-    for (i = 0; modes[i] != NULL; i++)
-      checked_free(modes[i]);
 
-    checked_free(modes);
-  }
-#endif
-
-  /* open SDL video output device (window or fullscreen mode) */
-  if (!SDLSetVideoMode(backbuffer, fullscreen))
+  // open SDL video output device (window or fullscreen mode)
+  if (!SDLSetVideoMode(fullscreen))
     Error(ERR_EXIT, "setting video mode failed");
 
-  /* !!! SDL2 can only set the window icon if the window already exists !!! */
-  /* set window icon */
+  // !!! SDL2 can only set the window icon if the window already exists !!!
+  // set window icon
   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
+  // set window and icon title
+  SDLSetWindowTitle();
+}
 
+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
      video framebuffer with 'SDL_UpdateRect' (or replaced with the current
@@ -494,61 +465,68 @@ void SDLInitVideoBuffer(DrawBuffer **backbuffer, DrawWindow **window,
      buffer 'window' at the same size as the SDL backbuffer. Although it
      should never be drawn to directly, it would do no harm nevertheless. */
 
-  /* create additional (symbolic) buffer for double-buffering */
-  ReCreateBitmap(window, video.width, video.height, video.depth);
+  // 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 SDL_Surface *SDLCreateScreen(DrawBuffer **backbuffer,
-                                   boolean fullscreen)
+static boolean SDLCreateScreen(boolean fullscreen)
 {
   SDL_Surface *new_surface = NULL;
 
-#if defined(TARGET_SDL2)
-  int surface_flags_window = SURFACE_FLAGS | SDL_WINDOW_RESIZABLE;
-#if USE_DESKTOP_FULLSCREEN
+  int surface_flags_window     = SURFACE_FLAGS | SDL_WINDOW_RESIZABLE;
   int surface_flags_fullscreen = SURFACE_FLAGS | SDL_WINDOW_FULLSCREEN_DESKTOP;
-#else
-  int surface_flags_fullscreen = SURFACE_FLAGS | SDL_WINDOW_FULLSCREEN;
-#endif
 
+#if 1
+  int renderer_flags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE;
 #else
-  int surface_flags_window = SURFACE_FLAGS;
-  int surface_flags_fullscreen = SURFACE_FLAGS | SDL_FULLSCREEN;
+  /* If SDL_CreateRenderer() is called from within a VirtualBox Windows VM
+     _without_ enabling 2D/3D acceleration and/or guest additions installed,
+     it will crash if flags are *not* set to SDL_RENDERER_SOFTWARE (because
+     it will try to use accelerated graphics and apparently fails miserably) */
+  int renderer_flags = SDL_RENDERER_SOFTWARE;
 #endif
 
-  int width  = (fullscreen ? fullscreen_width  : video.width);
-  int height = (fullscreen ? fullscreen_height : video.height);
+  SDLSetScreenSizeAndOffsets(video.width, video.height);
+
+  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;
-
-#if defined(TARGET_SDL2)
+  video.window_width  = screen_width;
+  video.window_height = screen_height;
 
   // store if initial screen mode is fullscreen mode when changing screen size
   video.fullscreen_initial = fullscreen;
 
-#if USE_RENDERER
   float window_scaling_factor = (float)setup.window_scaling_percent / 100;
-#if !USE_DESKTOP_FULLSCREEN
-  float screen_scaling_factor = (fullscreen ? 1 : window_scaling_factor);
-#endif
 
-  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 ((*backbuffer)->surface)
+  if (sdl_texture_stream)
   {
-    SDL_FreeSurface((*backbuffer)->surface);
-    (*backbuffer)->surface = NULL;
+    SDL_DestroyTexture(sdl_texture_stream);
+    sdl_texture_stream = NULL;
   }
 
-  if (sdl_texture)
+  if (sdl_texture_target)
   {
-    SDL_DestroyTexture(sdl_texture);
-    sdl_texture = NULL;
+    SDL_DestroyTexture(sdl_texture_target);
+    sdl_texture_target = NULL;
   }
 
   if (!(fullscreen && fullscreen_enabled))
@@ -570,48 +548,41 @@ static SDL_Surface *SDLCreateScreen(DrawBuffer **backbuffer,
     sdl_window = SDL_CreateWindow(program.window_title,
                                  SDL_WINDOWPOS_CENTERED,
                                  SDL_WINDOWPOS_CENTERED,
-#if USE_DESKTOP_FULLSCREEN
                                  video.window_width,
                                  video.window_height,
-#else
-                                 (int)(screen_scaling_factor * width),
-                                 (int)(screen_scaling_factor * height),
-#endif
                                  surface_flags);
 
   if (sdl_window != NULL)
   {
-#if 0
-    /* if SDL_CreateRenderer() is called from within a VirtualBox Windows VM
-     *without* enabling 2D/3D acceleration and/or guest additions installed,
-     it will crash if flags are *not* set to SDL_RENDERER_SOFTWARE (because
-     it will try to use accelerated graphics and apparently fails miserably) */
     if (sdl_renderer == NULL)
-      sdl_renderer = SDL_CreateRenderer(sdl_window, -1, SDL_RENDERER_SOFTWARE);
-#else
-    if (sdl_renderer == NULL)
-      sdl_renderer = SDL_CreateRenderer(sdl_window, -1, 0);
-#endif
+      sdl_renderer = SDL_CreateRenderer(sdl_window, -1, renderer_flags);
 
     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);
 
-      sdl_texture = SDL_CreateTexture(sdl_renderer,
-                                     SDL_PIXELFORMAT_ARGB8888,
-                                     SDL_TEXTUREACCESS_STREAMING,
-                                     width, height);
+      SDLSetScreenVsyncMode(setup.vsync_mode);
+
+      sdl_texture_stream = SDL_CreateTexture(sdl_renderer,
+                                            SDL_PIXELFORMAT_ARGB8888,
+                                            SDL_TEXTUREACCESS_STREAMING,
+                                            width, height);
 
-      if (sdl_texture != NULL)
+      if (SDL_RenderTargetSupported(sdl_renderer))
+       sdl_texture_target = SDL_CreateTexture(sdl_renderer,
+                                              SDL_PIXELFORMAT_ARGB8888,
+                                              SDL_TEXTUREACCESS_TARGET,
+                                              width, height);
+
+      if (sdl_texture_stream != NULL)
       {
        // use SDL default values for RGB masks and no alpha channel
        new_surface = SDL_CreateRGBSurface(0, width, height, 32, 0,0,0, 0);
 
        if (new_surface == NULL)
-         Error(ERR_WARN, "SDL_CreateRGBSurface() failed: %s",
-               SDL_GetError());
+         Error(ERR_WARN, "SDL_CreateRGBSurface() failed: %s", SDL_GetError());
       }
       else
       {
@@ -628,109 +599,66 @@ static SDL_Surface *SDLCreateScreen(DrawBuffer **backbuffer,
     Error(ERR_WARN, "SDL_CreateWindow() failed: %s", SDL_GetError());
   }
 
-#else
-
-  if (sdl_window)
-    SDL_DestroyWindow(sdl_window);
+  // store fullscreen state ("video.fullscreen_enabled" may not reflect this!)
+  if (new_surface != NULL)
+    fullscreen_enabled = fullscreen;
 
-  sdl_window = SDL_CreateWindow(program.window_title,
-                               SDL_WINDOWPOS_CENTERED,
-                               SDL_WINDOWPOS_CENTERED,
-                               width, height,
-                               surface_flags);
+  if (backbuffer == NULL)
+    backbuffer = CreateBitmapStruct();
 
-  if (sdl_window != NULL)
-    new_surface = SDL_GetWindowSurface(sdl_window);
-#endif
+  backbuffer->width  = video.width;
+  backbuffer->height = video.height;
 
-#else
-  new_surface = SDL_SetVideoMode(width, height, video.depth, surface_flags);
-#endif
+  if (backbuffer->surface)
+    SDL_FreeSurface(backbuffer->surface);
 
-#if defined(TARGET_SDL2)
-  // store fullscreen state ("video.fullscreen_enabled" may not reflect this!)
-  if (new_surface != NULL)
-    fullscreen_enabled = fullscreen;
-#endif
+  backbuffer->surface = new_surface;
 
-  return new_surface;
+  return (new_surface != NULL);
 }
 
-boolean SDLSetVideoMode(DrawBuffer **backbuffer, boolean fullscreen)
+boolean SDLSetVideoMode(boolean fullscreen)
 {
-  boolean success = TRUE;
-  SDL_Surface *new_surface = NULL;
+  boolean success = FALSE;
 
   SetWindowTitle();
 
-  if (*backbuffer == NULL)
-    *backbuffer = CreateBitmapStruct();
-
-  /* (real bitmap might be larger in fullscreen mode with video offsets) */
-  (*backbuffer)->width  = video.width;
-  (*backbuffer)->height = video.height;
-
   if (fullscreen && !video.fullscreen_enabled && video.fullscreen_available)
   {
-    setFullscreenParameters(setup.fullscreen_mode);
-
-    video_xoffset = fullscreen_xoffset;
-    video_yoffset = fullscreen_yoffset;
+    // switch display to fullscreen mode, if available
+    success = SDLCreateScreen(TRUE);
 
-    /* switch display to fullscreen mode, if available */
-    new_surface = SDLCreateScreen(backbuffer, TRUE);
-
-    if (new_surface == NULL)
+    if (!success)
     {
-      /* switching display to fullscreen mode failed */
-      Error(ERR_WARN, "SDL_SetVideoMode() failed: %s", SDL_GetError());
-
-      /* do not try it again */
+      // switching display to fullscreen mode failed -- do not try it again
       video.fullscreen_available = FALSE;
-
-      success = FALSE;
     }
     else
     {
-      (*backbuffer)->surface = new_surface;
-
       video.fullscreen_enabled = TRUE;
-      video.fullscreen_mode_current = setup.fullscreen_mode;
-
-      success = TRUE;
     }
   }
 
-  if ((!fullscreen && video.fullscreen_enabled) || new_surface == NULL)
+  if ((!fullscreen && video.fullscreen_enabled) || !success)
   {
-    video_xoffset = 0;
-    video_yoffset = 0;
-
-    /* switch display to window mode */
-    new_surface = SDLCreateScreen(backbuffer, FALSE);
+    // switch display to window mode
+    success = SDLCreateScreen(FALSE);
 
-    if (new_surface == NULL)
+    if (!success)
     {
-      /* switching display to window mode failed -- should not happen */
-      Error(ERR_WARN, "SDL_SetVideoMode() failed: %s", SDL_GetError());
-
-      success = FALSE;
+      // switching display to window mode failed -- should not happen
     }
     else
     {
-      (*backbuffer)->surface = new_surface;
-
       video.fullscreen_enabled = FALSE;
       video.window_scaling_percent = setup.window_scaling_percent;
       video.window_scaling_quality = setup.window_scaling_quality;
 
-      success = TRUE;
+      SDLSetScreenRenderingMode(setup.screen_rendering_mode);
     }
   }
 
-#if defined(TARGET_SDL2)
   SDLRedrawWindow();                   // map window
-#endif
 
 #ifdef DEBUG
 #if defined(PLATFORM_WIN32)
@@ -744,20 +672,13 @@ boolean SDLSetVideoMode(DrawBuffer **backbuffer, boolean fullscreen)
     boolean wminfo_success = FALSE;
 
     SDL_VERSION(&wminfo.version);
-#if defined(TARGET_SDL2)
+
     if (sdl_window)
       wminfo_success = SDL_GetWindowWMInfo(sdl_window, &wminfo);
-#else
-    wminfo_success = (SDL_GetWMInfo(&wminfo) == 1);
-#endif
 
     if (wminfo_success)
     {
-#if defined(TARGET_SDL2)
       hwnd = wminfo.info.win.window;
-#else
-      hwnd = wminfo.window;
-#endif
 
       DragAcceptFiles(hwnd, TRUE);
     }
@@ -768,24 +689,22 @@ boolean SDLSetVideoMode(DrawBuffer **backbuffer, 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);
-#endif
 }
 
-#if defined(TARGET_SDL2)
 void SDLSetWindowScaling(int window_scaling_percent)
 {
   if (sdl_window == NULL)
     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);
 
@@ -798,25 +717,42 @@ void SDLSetWindowScaling(int window_scaling_percent)
 
 void SDLSetWindowScalingQuality(char *window_scaling_quality)
 {
-  if (sdl_texture == NULL)
+  SDL_Texture *new_texture;
+
+  if (sdl_texture_stream == NULL)
     return;
 
   SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, window_scaling_quality);
 
-  SDL_Texture *new_texture = SDL_CreateTexture(sdl_renderer,
-                                              SDL_PIXELFORMAT_ARGB8888,
-                                              SDL_TEXTUREACCESS_STREAMING,
-                                              video.width, video.height);
+  new_texture = SDL_CreateTexture(sdl_renderer,
+                                 SDL_PIXELFORMAT_ARGB8888,
+                                 SDL_TEXTUREACCESS_STREAMING,
+                                 video.width, video.height);
 
   if (new_texture != NULL)
   {
-    SDL_DestroyTexture(sdl_texture);
+    SDL_DestroyTexture(sdl_texture_stream);
+
+    sdl_texture_stream = new_texture;
+  }
 
-    sdl_texture = new_texture;
+  if (SDL_RenderTargetSupported(sdl_renderer))
+    new_texture = SDL_CreateTexture(sdl_renderer,
+                                   SDL_PIXELFORMAT_ARGB8888,
+                                   SDL_TEXTUREACCESS_TARGET,
+                                   video.width, video.height);
+  else
+    new_texture = NULL;
+
+  if (new_texture != NULL)
+  {
+    SDL_DestroyTexture(sdl_texture_target);
 
-    SDLRedrawWindow();
+    sdl_texture_target = new_texture;
   }
 
+  SDLRedrawWindow();
+
   video.window_scaling_quality = window_scaling_quality;
 }
 
@@ -825,11 +761,7 @@ void SDLSetWindowFullscreen(boolean fullscreen)
   if (sdl_window == NULL)
     return;
 
-#if USE_DESKTOP_FULLSCREEN
   int flags = (fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
-#else
-  int flags = (fullscreen ? SDL_WINDOW_FULLSCREEN : 0);
-#endif
 
   if (SDL_SetWindowFullscreen(sdl_window, flags) == 0)
     video.fullscreen_enabled = fullscreen_enabled = fullscreen;
@@ -845,15 +777,101 @@ void SDLSetWindowFullscreen(boolean fullscreen)
   }
 }
 
-void SDLRedrawWindow()
+void SDLSetDisplaySize(void)
 {
-  UpdateScreen(NULL);
+  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(void)
+{
+  SDLSetScreenSizeAndOffsets(video.width, video.height);
+  SDLSetScreenSizeForRenderer(video.screen_width, video.screen_height);
+}
+
+void SDLSetScreenRenderingMode(char *screen_rendering_mode)
+{
+  video.screen_rendering_mode =
+    (strEqual(screen_rendering_mode, STR_SPECIAL_RENDERING_BITMAP) ?
+     SPECIAL_RENDERING_BITMAP :
+     strEqual(screen_rendering_mode, STR_SPECIAL_RENDERING_TARGET) ?
+     SPECIAL_RENDERING_TARGET:
+     strEqual(screen_rendering_mode, STR_SPECIAL_RENDERING_DOUBLE) ?
+     SPECIAL_RENDERING_DOUBLE : SPECIAL_RENDERING_OFF);
+}
+
+void SDLSetScreenVsyncMode(char *vsync_mode)
+{
+  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);
+}
+
+void SDLRedrawWindow(void)
+{
+  UpdateScreen_WithoutFrameDelay(NULL);
+}
 
 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);
 
@@ -871,8 +889,17 @@ void SDLFreeBitmapPointers(Bitmap *bitmap)
     SDL_FreeSurface(bitmap->surface);
   if (bitmap->surface_masked)
     SDL_FreeSurface(bitmap->surface_masked);
+
   bitmap->surface = NULL;
   bitmap->surface_masked = NULL;
+
+  if (bitmap->texture)
+    SDL_DestroyTexture(bitmap->texture);
+  if (bitmap->texture_masked)
+    SDL_DestroyTexture(bitmap->texture_masked);
+
+  bitmap->texture = NULL;
+  bitmap->texture_masked = NULL;
 }
 
 void SDLCopyArea(Bitmap *src_bitmap, Bitmap *dst_bitmap,
@@ -882,23 +909,11 @@ void SDLCopyArea(Bitmap *src_bitmap, Bitmap *dst_bitmap,
   Bitmap *real_dst_bitmap = (dst_bitmap == window ? backbuffer : dst_bitmap);
   SDL_Rect src_rect, dst_rect;
 
-  if (src_bitmap == backbuffer)
-  {
-    src_x += video_xoffset;
-    src_y += video_yoffset;
-  }
-
   src_rect.x = src_x;
   src_rect.y = src_y;
   src_rect.w = width;
   src_rect.h = height;
 
-  if (dst_bitmap == backbuffer || dst_bitmap == window)
-  {
-    dst_x += video_xoffset;
-    dst_y += video_yoffset;
-  }
-
   dst_rect.x = dst_x;
   dst_rect.y = dst_y;
   dst_rect.w = width;
@@ -910,20 +925,35 @@ void SDLCopyArea(Bitmap *src_bitmap, Bitmap *dst_bitmap,
                     src_bitmap->surface_masked : src_bitmap->surface),
                    &src_rect, real_dst_bitmap->surface, &dst_rect);
 
-#if defined(TARGET_SDL2)
   if (dst_bitmap == window)
-  {
-    // SDL_UpdateWindowSurface(sdl_window);
-    // SDL_UpdateWindowSurfaceRects(sdl_window, &dst_rect, 1);
-    UpdateScreen(&dst_rect);
-  }
-#else
-  if (dst_bitmap == window)
-  {
-    // SDL_UpdateRect(backbuffer->surface, dst_x, dst_y, width, height);
-    UpdateScreen(&dst_rect);
-  }
-#endif
+    UpdateScreen_WithFrameDelay(&dst_rect);
+}
+
+void SDLBlitTexture(Bitmap *bitmap,
+                   int src_x, int src_y, int width, int height,
+                   int dst_x, int dst_y, int mask_mode)
+{
+  SDL_Texture *texture;
+  SDL_Rect src_rect;
+  SDL_Rect dst_rect;
+
+  texture =
+    (mask_mode == BLIT_MASKED ? bitmap->texture_masked : bitmap->texture);
+
+  if (texture == NULL)
+    return;
+
+  src_rect.x = src_x;
+  src_rect.y = src_y;
+  src_rect.w = width;
+  src_rect.h = height;
+
+  dst_rect.x = dst_x;
+  dst_rect.y = dst_y;
+  dst_rect.w = width;
+  dst_rect.h = height;
+
+  SDL_RenderCopy(sdl_renderer, texture, &src_rect, &dst_rect);
 }
 
 void SDLFillRectangle(Bitmap *dst_bitmap, int x, int y, int width, int height,
@@ -932,12 +962,6 @@ void SDLFillRectangle(Bitmap *dst_bitmap, int x, int y, int width, int height,
   Bitmap *real_dst_bitmap = (dst_bitmap == window ? backbuffer : dst_bitmap);
   SDL_Rect rect;
 
-  if (dst_bitmap == backbuffer || dst_bitmap == window)
-  {
-    x += video_xoffset;
-    y += video_yoffset;
-  }
-
   rect.x = x;
   rect.y = y;
   rect.w = width;
@@ -945,135 +969,77 @@ void SDLFillRectangle(Bitmap *dst_bitmap, int x, int y, int width, int height,
 
   SDL_FillRect(real_dst_bitmap->surface, &rect, color);
 
-#if defined(TARGET_SDL2)
-  if (dst_bitmap == window)
-  {
-    // SDL_UpdateWindowSurface(sdl_window);
-    // SDL_UpdateWindowSurfaceRects(sdl_window, &rect, 1);
-    UpdateScreen(&rect);
-  }
-#else
   if (dst_bitmap == window)
-  {
-    // SDL_UpdateRect(backbuffer->surface, x, y, width, height);
-    UpdateScreen(&rect);
-  }
-#endif
+    UpdateScreen_WithFrameDelay(&rect);
+}
+
+void PrepareFadeBitmap(int draw_target)
+{
+  Bitmap *fade_bitmap =
+    (draw_target == DRAW_TO_FADE_SOURCE ? gfx.fade_bitmap_source :
+     draw_target == DRAW_TO_FADE_TARGET ? gfx.fade_bitmap_target : NULL);
+
+  if (fade_bitmap == NULL)
+    return;
+
+  // copy backbuffer to fading buffer
+  BlitBitmap(backbuffer, fade_bitmap, 0, 0, gfx.win_xsize, gfx.win_ysize, 0, 0);
+
+  // add border and animations to fading buffer
+  FinalizeScreen(draw_target);
 }
 
-void SDLFadeRectangle(Bitmap *bitmap_cross, int x, int y, int width, int height,
+void SDLFadeRectangle(int x, int y, int width, int height,
                      int fade_mode, int fade_delay, int post_delay,
                      void (*draw_border_function)(void))
 {
-  static boolean initialization_needed = TRUE;
-  static SDL_Surface *surface_source = NULL;
-  static SDL_Surface *surface_target = NULL;
-  static SDL_Surface *surface_black = NULL;
+  SDL_Surface *surface_backup = gfx.fade_bitmap_backup->surface;
+  SDL_Surface *surface_source = gfx.fade_bitmap_source->surface;
+  SDL_Surface *surface_target = gfx.fade_bitmap_target->surface;
+  SDL_Surface *surface_black  = gfx.fade_bitmap_black->surface;
   SDL_Surface *surface_screen = backbuffer->surface;
-  SDL_Surface *surface_cross = (bitmap_cross ? bitmap_cross->surface : NULL);
   SDL_Rect src_rect, dst_rect;
   SDL_Rect dst_rect2;
   int src_x = x, src_y = y;
   int dst_x = x, dst_y = y;
   unsigned int time_last, time_current;
 
-  /* check if screen size has changed */
-  if (surface_source != NULL && (video.width  != surface_source->w ||
-                                video.height != surface_source->h))
-  {
-    SDL_FreeSurface(surface_source);
-    SDL_FreeSurface(surface_target);
-    SDL_FreeSurface(surface_black);
+  // store function for drawing global masked border
+  void (*draw_global_border_function)(int) = gfx.draw_global_border_function;
 
-    initialization_needed = TRUE;
-  }
+  // deactivate drawing of global border while fading, if needed
+  if (draw_border_function == NULL)
+    gfx.draw_global_border_function = NULL;
 
   src_rect.x = src_x;
   src_rect.y = src_y;
   src_rect.w = width;
   src_rect.h = height;
 
-  dst_x += video_xoffset;
-  dst_y += video_yoffset;
-
   dst_rect.x = dst_x;
   dst_rect.y = dst_y;
-  dst_rect.w = width;          /* (ignored) */
-  dst_rect.h = height;         /* (ignored) */
+  dst_rect.w = width;          // (ignored)
+  dst_rect.h = height;         // (ignored)
 
   dst_rect2 = dst_rect;
 
-  if (initialization_needed)
-  {
-#if defined(TARGET_SDL2)
-    unsigned int flags = 0;
-#else
-    unsigned int flags = SDL_SRCALPHA;
-
-    /* use same surface type as screen surface */
-    if ((surface_screen->flags & SDL_HWSURFACE))
-      flags |= SDL_HWSURFACE;
-    else
-      flags |= SDL_SWSURFACE;
-#endif
+  // before fading in, store backbuffer (without animation graphics)
+  if (fade_mode & (FADE_TYPE_FADE_IN | FADE_TYPE_TRANSFORM))
+    SDL_BlitSurface(surface_screen, &dst_rect, surface_backup, &src_rect);
 
-    /* create surface for temporary copy of screen buffer (source) */
-    if ((surface_source =
-        SDL_CreateRGBSurface(flags,
-                             video.width,
-                             video.height,
-                             surface_screen->format->BitsPerPixel,
-                             surface_screen->format->Rmask,
-                             surface_screen->format->Gmask,
-                             surface_screen->format->Bmask,
-                             surface_screen->format->Amask)) == NULL)
-      Error(ERR_EXIT, "SDL_CreateRGBSurface() failed: %s", SDL_GetError());
-
-    /* create surface for cross-fading screen buffer (target) */
-    if ((surface_target =
-        SDL_CreateRGBSurface(flags,
-                             video.width,
-                             video.height,
-                             surface_screen->format->BitsPerPixel,
-                             surface_screen->format->Rmask,
-                             surface_screen->format->Gmask,
-                             surface_screen->format->Bmask,
-                             surface_screen->format->Amask)) == NULL)
-      Error(ERR_EXIT, "SDL_CreateRGBSurface() failed: %s", SDL_GetError());
-
-    /* create black surface for fading from/to black */
-    if ((surface_black =
-        SDL_CreateRGBSurface(flags,
-                             video.width,
-                             video.height,
-                             surface_screen->format->BitsPerPixel,
-                             surface_screen->format->Rmask,
-                             surface_screen->format->Gmask,
-                             surface_screen->format->Bmask,
-                             surface_screen->format->Amask)) == NULL)
-      Error(ERR_EXIT, "SDL_CreateRGBSurface() failed: %s", SDL_GetError());
-
-    /* completely fill the surface with black color pixels */
-    SDL_FillRect(surface_black, NULL,
-                SDL_MapRGB(surface_screen->format, 0, 0, 0));
-
-    initialization_needed = FALSE;
-  }
-
-  /* copy source and target surfaces to temporary surfaces for fading */
+  // copy source and target surfaces to temporary surfaces for fading
   if (fade_mode & FADE_TYPE_TRANSFORM)
   {
-    SDL_BlitSurface(surface_cross,  &src_rect, surface_source, &src_rect);
-    SDL_BlitSurface(surface_screen, &dst_rect, surface_target, &src_rect);
+    // (source and target fading buffer already prepared)
   }
   else if (fade_mode & FADE_TYPE_FADE_IN)
   {
+    // (target fading buffer already prepared)
     SDL_BlitSurface(surface_black,  &src_rect, surface_source, &src_rect);
-    SDL_BlitSurface(surface_screen, &dst_rect, surface_target, &src_rect);
   }
-  else         /* FADE_TYPE_FADE_OUT */
+  else         // FADE_TYPE_FADE_OUT
   {
-    SDL_BlitSurface(surface_screen, &dst_rect, surface_source, &src_rect);
+    // (source fading buffer already prepared)
     SDL_BlitSurface(surface_black,  &src_rect, surface_target, &src_rect);
   }
 
@@ -1091,17 +1057,14 @@ void SDLFadeRectangle(Bitmap *bitmap_cross, 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);
 
     for (i = 1 ; i < melt_columns; i++)
     {
-      int r = GetSimpleRandom(3) - 1;  /* randomly choose from { -1, 0, -1 } */
+      int r = GetSimpleRandom(3) - 1;  // randomly choose from { -1, 0, -1 }
 
       ypos[i] = ypos[i - 1] + r;
 
@@ -1143,7 +1106,7 @@ void SDLFadeRectangle(Bitmap *bitmap_cross, int x, int y, int width, int height,
          if (ypos[i] + dy >= height)
            dy = height - ypos[i];
 
-         /* copy part of (appearing) target surface to upper area */
+         // copy part of (appearing) target surface to upper area
          src_rect.x = src_x + i * melt_pixels;
          // src_rect.y = src_y + ypos[i];
          src_rect.y = src_y;
@@ -1161,7 +1124,7 @@ void SDLFadeRectangle(Bitmap *bitmap_cross, int x, int y, int width, int height,
 
          ypos[i] += dy;
 
-         /* copy part of (disappearing) source surface to lower area */
+         // copy part of (disappearing) source surface to lower area
          src_rect.x = src_x + i * melt_pixels;
          src_rect.y = src_y;
          src_rect.w = melt_pixels;
@@ -1197,11 +1160,66 @@ void SDLFadeRectangle(Bitmap *bitmap_cross, int x, int y, int width, int height,
        if (draw_border_function != NULL)
          draw_border_function();
 
-       UpdateScreen(&dst_rect2);
+       UpdateScreen_WithFrameDelay(&dst_rect2);
       }
     }
   }
-  else
+  else if (fade_mode == FADE_MODE_CURTAIN)
+  {
+    float xx;
+    int xx_final;
+    int xx_size = width / 2;
+
+    SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
+
+    SDLSetAlpha(surface_source, FALSE, 0);     // disable alpha blending
+
+    for (xx = 0; xx < xx_size;)
+    {
+      time_last = time_current;
+      time_current = SDL_GetTicks();
+      xx += xx_size * ((float)(time_current - time_last) / fade_delay);
+      xx_final = MIN(MAX(0, xx), xx_size);
+
+      src_rect.x = src_x;
+      src_rect.y = src_y;
+      src_rect.w = width;
+      src_rect.h = height;
+
+      dst_rect.x = dst_x;
+      dst_rect.y = dst_y;
+
+      // draw new (target) image to screen buffer
+      SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
+
+      if (xx_final < xx_size)
+      {
+       src_rect.w = xx_size - xx_final;
+       src_rect.h = height;
+
+       // draw old (source) image to screen buffer (left side)
+
+       src_rect.x = src_x + xx_final;
+       dst_rect.x = dst_x;
+
+       SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
+
+       // draw old (source) image to screen buffer (right side)
+
+       src_rect.x = src_x + xx_size;
+       dst_rect.x = dst_x + xx_size + xx_final;
+
+       SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
+      }
+
+      if (draw_border_function != NULL)
+       draw_border_function();
+
+      // only update the region of the screen that is affected from fading
+      UpdateScreen_WithFrameDelay(&dst_rect2);
+    }
+  }
+  else         // fading in, fading out or cross-fading
   {
     float alpha;
     int alpha_final;
@@ -1213,27 +1231,30 @@ void SDLFadeRectangle(Bitmap *bitmap_cross, int x, int y, int width, int height,
       alpha += 255 * ((float)(time_current - time_last) / fade_delay);
       alpha_final = MIN(MAX(0, alpha), 255);
 
-      /* draw existing (source) image to screen buffer */
+      // draw existing (source) image to screen buffer
       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
+      // draw new (target) image to screen buffer using alpha blending
+      SDLSetAlpha(surface_target, TRUE, alpha_final);
       SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
 
       if (draw_border_function != NULL)
        draw_border_function();
 
-      /* only update the region of the screen that is affected from fading */
-      UpdateScreen(&dst_rect);
+      // only update the region of the screen that is affected from fading
+      UpdateScreen_WithFrameDelay(&dst_rect);
     }
   }
 
-  Delay(post_delay);
+  if (post_delay > 0)
+    Delay_WithScreenUpdates(post_delay);
+
+  // restore function for drawing global masked border
+  gfx.draw_global_border_function = draw_global_border_function;
+
+  // after fading in, restore backbuffer (without animation graphics)
+  if (fade_mode & (FADE_TYPE_FADE_IN | FADE_TYPE_TRANSFORM))
+    SDL_BlitSurface(surface_backup, &dst_rect, surface_screen, &src_rect);
 }
 
 void SDLDrawSimpleLine(Bitmap *dst_bitmap, int from_x, int from_y,
@@ -1253,26 +1274,12 @@ void SDLDrawSimpleLine(Bitmap *dst_bitmap, int from_x, int from_y,
   rect.w = (to_x - from_x + 1);
   rect.h = (to_y - from_y + 1);
 
-  if (dst_bitmap == backbuffer || dst_bitmap == window)
-  {
-    rect.x += video_xoffset;
-    rect.y += video_yoffset;
-  }
-
   SDL_FillRect(surface, &rect, color);
 }
 
 void SDLDrawLine(Bitmap *dst_bitmap, int from_x, int from_y,
                 int to_x, int to_y, Uint32 color)
 {
-  if (dst_bitmap == backbuffer || dst_bitmap == window)
-  {
-    from_x += video_xoffset;
-    from_y += video_yoffset;
-    to_x += video_xoffset;
-    to_y += video_yoffset;
-  }
-
   sge_Line(dst_bitmap->surface, from_x, from_y, to_x, to_y, color);
 }
 
@@ -1310,29 +1317,23 @@ Pixel SDLGetPixel(Bitmap *src_bitmap, int x, int y)
 {
   SDL_Surface *surface = src_bitmap->surface;
 
-  if (src_bitmap == backbuffer || src_bitmap == window)
-  {
-    x += video_xoffset;
-    y += video_yoffset;
-  }
-
   switch (surface->format->BytesPerPixel)
   {
-    case 1:            /* assuming 8-bpp */
+    case 1:            // assuming 8-bpp
     {
       return *((Uint8 *)surface->pixels + y * surface->pitch + x);
     }
     break;
 
-    case 2:            /* probably 15-bpp or 16-bpp */
+    case 2:            // probably 15-bpp or 16-bpp
     {
       return *((Uint16 *)surface->pixels + y * surface->pitch / 2 + x);
     }
     break;
 
-  case 3:              /* slow 24-bpp mode; usually not used */
+  case 3:              // slow 24-bpp mode; usually not used
     {
-      /* does this work? */
+      // does this work?
       Uint8 *pix = (Uint8 *)surface->pixels + y * surface->pitch + x * 3;
       Uint32 color = 0;
       int shift;
@@ -1348,7 +1349,7 @@ Pixel SDLGetPixel(Bitmap *src_bitmap, int x, int y)
     }
     break;
 
-  case 4:              /* probably 32-bpp */
+  case 4:              // probably 32-bpp
     {
       return *((Uint32 *)surface->pixels + y * surface->pitch / 4 + x);
     }
@@ -1359,13 +1360,13 @@ Pixel SDLGetPixel(Bitmap *src_bitmap, int x, int y)
 }
 
 
-/* ========================================================================= */
-/* The following functions were taken from the SGE library                   */
-/* (SDL Graphics Extension Library) by Anders Lindström                      */
-/* http://www.etek.chalmers.se/~e8cal1/sge/index.html                        */
-/* ========================================================================= */
+// ============================================================================
+// The following functions were taken from the SGE library
+// (SDL Graphics Extension Library) by Anders Lindström
+// 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)
   {
@@ -1373,25 +1374,25 @@ void _PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
     {
       case 1:
       {
-       /* Assuming 8-bpp */
+       // Assuming 8-bpp
        *((Uint8 *)surface->pixels + y*surface->pitch + x) = color;
       }
       break;
 
       case 2:
       {
-       /* Probably 15-bpp or 16-bpp */
+       // Probably 15-bpp or 16-bpp
        *((Uint16 *)surface->pixels + y*surface->pitch/2 + x) = color;
       }
       break;
 
       case 3:
       {
-       /* Slow 24-bpp mode, usually not used */
+       // Slow 24-bpp mode, usually not used
        Uint8 *pix;
        int shift;
 
-       /* Gack - slow, but endian correct */
+       // Gack - slow, but endian correct
        pix = (Uint8 *)surface->pixels + y * surface->pitch + x*3;
        shift = surface->format->Rshift;
        *(pix+shift/8) = color>>shift;
@@ -1404,7 +1405,7 @@ void _PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
 
       case 4:
       {
-       /* Probably 32-bpp */
+       // Probably 32-bpp
        *((Uint32 *)surface->pixels + y*surface->pitch/4 + x) = color;
       }
       break;
@@ -1412,28 +1413,29 @@ 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;
 
-  /* Gack - slow, but endian correct */
+  // Gack - slow, but endian correct
   pix = (Uint8 *)surface->pixels + y * surface->pitch + x*3;
   shift = surface->format->Rshift;
   *(pix+shift/8) = color>>shift;
@@ -1443,12 +1445,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)
   {
@@ -1469,8 +1471,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))
   {
@@ -1488,13 +1491,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)
   {
@@ -1521,7 +1525,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)
   {
@@ -1529,25 +1534,25 @@ void sge_pPutPixel(SDL_Surface *surface, Sint16 x, Sint32 ypitch, Uint32 color)
     {
       case 1:
       {
-       /* Assuming 8-bpp */
+       // Assuming 8-bpp
        *((Uint8 *)surface->pixels + ypitch + x) = color;
       }
       break;
 
       case 2:
       {
-       /* Probably 15-bpp or 16-bpp */
+       // Probably 15-bpp or 16-bpp
        *((Uint16 *)surface->pixels + ypitch + x) = color;
       }
       break;
 
       case 3:
       {
-       /* Slow 24-bpp mode, usually not used */
+       // Slow 24-bpp mode, usually not used
        Uint8 *pix;
        int shift;
 
-       /* Gack - slow, but endian correct */
+       // Gack - slow, but endian correct
        pix = (Uint8 *)surface->pixels + ypitch + x*3;
        shift = surface->format->Rshift;
        *(pix+shift/8) = color>>shift;
@@ -1560,7 +1565,7 @@ void sge_pPutPixel(SDL_Surface *surface, Sint16 x, Sint32 ypitch, Uint32 color)
 
       case 4:
       {
-       /* Probably 32-bpp */
+       // Probably 32-bpp
        *((Uint32 *)surface->pixels + ypitch + x) = color;
       }
       break;
@@ -1568,8 +1573,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;
 
@@ -1588,7 +1593,7 @@ void sge_HLine(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
     x2 = tmp;
   }
 
-  /* Do the clipping */
+  // Do the clipping
   if (y < 0 || y > Surface->h - 1 || x1 > Surface->w - 1 || x2 < 0)
     return;
   if (x1 < 0)
@@ -1609,13 +1614,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;
 
@@ -1626,7 +1632,7 @@ void _HLine(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y, Uint32 Color)
     x2 = tmp;
   }
 
-  /* Do the clipping */
+  // Do the clipping
   if (y < 0 || y > Surface->h - 1 || x1 > Surface->w - 1 || x2 < 0)
     return;
   if (x1 < 0)
@@ -1642,8 +1648,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;
 
@@ -1662,7 +1668,7 @@ void sge_VLine(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
     y2 = tmp;
   }
 
-  /* Do the clipping */
+  // Do the clipping
   if (x < 0 || x > Surface->w - 1 || y1 > Surface->h - 1 || y2 < 0)
     return;
   if (y1 < 0)
@@ -1683,13 +1689,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;
 
@@ -1700,7 +1707,7 @@ void _VLine(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2, Uint32 Color)
     y2 = tmp;
   }
 
-  /* Do the clipping */
+  // Do the clipping
   if (x < 0 || x > Surface->w - 1 || y1 > Surface->h - 1 || y2 < 0)
     return;
   if (y1 < 0)
@@ -1715,11 +1722,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;
 
@@ -1771,14 +1779,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)
@@ -1789,39 +1799,33 @@ void sge_Line(SDL_Surface *Surface, Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2,
       return;
    }
 
-   /* Draw the line */
+   // Draw the line
    sge_DoLine(Surface, x1, y1, x2, y2, Color, _PutPixel);
 
-   /* unlock the display */
+   // unlock the display
    if (SDL_MUSTLOCK(Surface))
    {
       SDL_UnlockSurface(Surface);
    }
 }
 
-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)
 {
-  if (dst_bitmap == backbuffer || dst_bitmap == window)
-  {
-    x += video_xoffset;
-    y += video_yoffset;
-  }
-
   sge_PutPixel(dst_bitmap->surface, x, y, pixel);
 }
 
 
-/*
-  -----------------------------------------------------------------------------
-  quick (no, it's slow) and dirty hack to "invert" rectangle inside SDL surface
-  -----------------------------------------------------------------------------
-*/
+// ----------------------------------------------------------------------------
+// quick (no, it's slow) and dirty hack to "invert" rectangle inside SDL surface
+// ----------------------------------------------------------------------------
 
 void SDLInvertArea(Bitmap *bitmap, int src_x, int src_y,
                   int width, int height, Uint32 color)
@@ -1858,19 +1862,17 @@ void SDLCopyInverseMasked(Bitmap *src_bitmap, Bitmap *dst_bitmap,
 }
 
 
-/* ========================================================================= */
-/* The following functions were taken from the SDL_gfx library version 2.0.3 */
-/* (Rotozoomer) by Andreas Schiffler                                         */
-/* http://www.ferzkopp.net/Software/SDL_gfx-2.0/index.html                   */
-/* ========================================================================= */
-
-/*
-  -----------------------------------------------------------------------------
-  32 bit zoomer
+// ============================================================================
+// The following functions were taken from the SDL_gfx library version 2.0.3
+// (Rotozoomer) by Andreas Schiffler
+// http://www.ferzkopp.net/Software/SDL_gfx-2.0/index.html
+// ============================================================================
 
-  zoomes 32bit RGBA/ABGR 'src' surface to 'dst' surface.
-  -----------------------------------------------------------------------------
-*/
+// ----------------------------------------------------------------------------
+// 32 bit zoomer
+//
+// zoomes 32bit RGBA/ABGR 'src' surface to 'dst' surface.
+// ----------------------------------------------------------------------------
 
 typedef struct
 {
@@ -1880,13 +1882,13 @@ 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;
   int dgap;
 
-  /* pointer setup */
+  // pointer setup
   sp = csp = (tColorRGBA *) src->pixels;
   dp = (tColorRGBA *) dst->pixels;
   dgap = dst->pitch - dst->w * 4;
@@ -1905,60 +1907,60 @@ int zoomSurfaceRGBA_scaleDownBy2(SDL_Surface *src, SDL_Surface *dst)
       tColorRGBA *sp11 = &sp1[1];
       tColorRGBA new;
 
-      /* create new color pixel from all four source color pixels */
+      // create new color pixel from all four source color pixels
       new.r = (sp00->r + sp01->r + sp10->r + sp11->r) / 4;
       new.g = (sp00->g + sp01->g + sp10->g + sp11->g) / 4;
       new.b = (sp00->b + sp01->b + sp10->b + sp11->b) / 4;
       new.a = (sp00->a + sp01->a + sp10->a + sp11->a) / 4;
 
-      /* draw */
+      // draw
       *dp = new;
 
-      /* advance source pointers */
+      // advance source pointers
       sp += 2;
 
-      /* advance destination pointer */
+      // advance destination pointer
       dp++;
     }
 
-    /* advance source pointer */
+    // advance source pointer
     csp = (tColorRGBA *) ((Uint8 *) csp + 2 * src->pitch);
 
-    /* advance destination pointers */
+    // advance destination pointers
     dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
   }
 
   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;
   tColorRGBA *sp, *csp, *csp0, *dp;
   int dgap;
 
-  /* use specialized zoom function when scaling down to exactly half size */
+  // use specialized zoom function when scaling down to exactly half size
   if (src->w == 2 * dst->w &&
       src->h == 2 * dst->h)
     return zoomSurfaceRGBA_scaleDownBy2(src, dst);
 
-  /* variable setup */
+  // variable setup
   sx = (float) src->w / (float) dst->w;
   sy = (float) src->h / (float) dst->h;
 
-  /* allocate memory for row increments */
+  // allocate memory for row increments
   csax = sax = (int *)checked_malloc((dst->w + 1) * sizeof(Uint32));
   csay = say = (int *)checked_malloc((dst->h + 1) * sizeof(Uint32));
 
-  /* precalculate row increments */
+  // precalculate row increments
   for (x = 0; x <= dst->w; x++)
     *csax++ = (int)(sx * x);
 
   for (y = 0; y <= dst->h; y++)
     *csay++ = (int)(sy * y);
 
-  /* pointer setup */
+  // pointer setup
   sp = csp = csp0 = (tColorRGBA *) src->pixels;
   dp = (tColorRGBA *) dst->pixels;
   dgap = dst->pitch - dst->w * 4;
@@ -1971,22 +1973,22 @@ int zoomSurfaceRGBA(SDL_Surface *src, SDL_Surface *dst)
 
     for (x = 0; x < dst->w; x++)
     {
-      /* draw */
+      // draw
       *dp = *sp;
 
-      /* advance source pointers */
+      // advance source pointers
       csax++;
       sp = csp + *csax;
 
-      /* advance destination pointer */
+      // advance destination pointer
       dp++;
     }
 
-    /* advance source pointer */
+    // advance source pointer
     csay++;
     csp = (tColorRGBA *) ((Uint8 *) csp0 + *csay * src->pitch);
 
-    /* advance destination pointers */
+    // advance destination pointers
     dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
   }
 
@@ -1996,29 +1998,27 @@ int zoomSurfaceRGBA(SDL_Surface *src, SDL_Surface *dst)
   return 0;
 }
 
-/*
-  -----------------------------------------------------------------------------
-  8 bit zoomer
-
-  zoomes 8 bit palette/Y 'src' surface to 'dst' surface
-  -----------------------------------------------------------------------------
-*/
+// ----------------------------------------------------------------------------
+// 8 bit zoomer
+//
+// zoomes 8 bit palette/Y 'src' surface to 'dst' surface
+// ----------------------------------------------------------------------------
 
-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;
   int dgap;
 
-  /* variable setup */
+  // variable setup
   sx = (Uint32) (65536.0 * (float) src->w / (float) dst->w);
   sy = (Uint32) (65536.0 * (float) src->h / (float) dst->h);
 
-  /* allocate memory for row increments */
+  // allocate memory for row increments
   sax = (Uint32 *)checked_malloc(dst->w * sizeof(Uint32));
   say = (Uint32 *)checked_malloc(dst->h * sizeof(Uint32));
 
-  /* precalculate row increments */
+  // precalculate row increments
   csx = 0;
   csax = sax;
   for (x = 0; x < dst->w; x++)
@@ -2055,12 +2055,12 @@ int zoomSurfaceY(SDL_Surface * src, SDL_Surface * dst)
     csay++;
   }
 
-  /* pointer setup */
+  // pointer setup
   sp = csp = (Uint8 *) src->pixels;
   dp = (Uint8 *) dst->pixels;
   dgap = dst->pitch - dst->w;
 
-  /* draw */
+  // draw
   csay = say;
   for (y = 0; y < dst->h; y++)
   {
@@ -2068,22 +2068,22 @@ int zoomSurfaceY(SDL_Surface * src, SDL_Surface * dst)
     sp = csp;
     for (x = 0; x < dst->w; x++)
     {
-      /* draw */
+      // draw
       *dp = *sp;
 
-      /* advance source pointers */
+      // advance source pointers
       sp += (*csax);
       csax++;
 
-      /* advance destination pointer */
+      // advance destination pointer
       dp++;
     }
 
-    /* advance source pointer (for row) */
+    // advance source pointer (for row)
     csp += ((*csay) * src->pitch);
     csay++;
 
-    /* advance destination pointers */
+    // advance destination pointers
     dp += dgap;
   }
 
@@ -2093,18 +2093,16 @@ int zoomSurfaceY(SDL_Surface * src, SDL_Surface * dst)
   return 0;
 }
 
-/*
-  -----------------------------------------------------------------------------
-  zoomSurface()
-
-  Zooms a 32bit or 8bit 'src' surface to newly created 'dst' surface.
-  'zoomx' and 'zoomy' are scaling factors for width and height.
-  If the surface is not 8bit or 32bit RGBA/ABGR it will be converted
-  into a 32bit RGBA format on the fly.
-  -----------------------------------------------------------------------------
-*/
+// ----------------------------------------------------------------------------
+// zoomSurface()
+//
+// Zooms a 32bit or 8bit 'src' surface to newly created 'dst' surface.
+// 'zoomx' and 'zoomy' are scaling factors for width and height.
+// If the surface is not 8bit or 32bit RGBA/ABGR it will be converted
+// into a 32bit RGBA format on the fly.
+// ----------------------------------------------------------------------------
 
-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;
@@ -2115,146 +2113,178 @@ SDL_Surface *zoomSurface(SDL_Surface *src, int dst_width, int dst_height)
   if (src == NULL)
     return NULL;
 
-  /* determine if source surface is 32 bit or 8 bit */
+  // determine if source surface is 32 bit or 8 bit
   is_32bit = (src->format->BitsPerPixel == 32);
 
   if (is_32bit || src->format->BitsPerPixel == 8)
   {
-    /* use source surface 'as is' */
+    // use source surface 'as is'
     zoom_src = src;
   }
   else
   {
-    /* new source surface is 32 bit with a defined RGB ordering */
+    // 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;
   }
 
-  /* allocate surface to completely contain the zoomed surface */
+  // allocate surface to completely contain the zoomed surface
   if (is_32bit)
   {
-    /* target surface is 32 bit with source RGBA/ABGR ordering */
+    // target surface is 32 bit with source RGBA/ABGR ordering
     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
   {
-    /* target surface is 8 bit */
+    // target surface is 8 bit
     zoom_dst = SDL_CreateRGBSurface(SURFACE_FLAGS, dst_width, dst_height, 8,
                                    0, 0, 0, 0);
   }
 
-  /* lock source surface */
+  // lock source surface
   SDL_LockSurface(zoom_src);
 
-  /* check which kind of surface we have */
+  // check which kind of surface we have
   if (is_32bit)
   {
-    /* call the 32 bit transformation routine to do the zooming */
+    // call the 32 bit transformation routine to do the zooming
     zoomSurfaceRGBA(zoom_src, zoom_dst);
   }
   else
   {
-    /* copy palette */
+    // copy palette
     for (i = 0; i < zoom_src->format->palette->ncolors; i++)
       zoom_dst->format->palette->colors[i] =
        zoom_src->format->palette->colors[i];
     zoom_dst->format->palette->ncolors = zoom_src->format->palette->ncolors;
 
-    /* call the 8 bit transformation routine to do the zooming */
+    // call the 8 bit transformation routine to do the zooming
     zoomSurfaceY(zoom_src, zoom_dst);
   }
 
-  /* unlock source surface */
+  // unlock source surface
   SDL_UnlockSurface(zoom_src);
 
-  /* free temporary surface */
+  // free temporary surface
   if (is_converted)
     SDL_FreeSurface(zoom_src);
 
-  /* return destination surface */
+  // return destination surface
   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 */
+  dst_width  = MAX(1, dst_width);      // prevent zero bitmap width
+  dst_height = MAX(1, dst_height);     // prevent zero bitmap height
 
   dst_bitmap->width  = dst_width;
   dst_bitmap->height = dst_height;
 
-  /* create zoomed temporary surface from source surface */
-  *dst_surface = zoomSurface(src_bitmap->surface, dst_width, dst_height);
+  // create zoomed temporary surface from source surface
+  dst_surface = zoomSurface(src_surface, dst_width, dst_height);
+
+  // create native format destination surface from zoomed temporary surface
+  SDLSetNativeSurface(&dst_surface);
 
-  /* create native format destination surface from zoomed temporary 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;
 }
 
 
-/* ========================================================================= */
-/* load image to bitmap                                                      */
-/* ========================================================================= */
+// ============================================================================
+// load image to bitmap
+// ============================================================================
 
 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 */
+  // 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());
-
-    return NULL;
-  }
+  // create native non-transparent surface for current image
+  if ((new_bitmap->surface = SDLGetOpaqueSurface(sdl_image_tmp)) == NULL)
+    Error(ERR_EXIT, "SDLGetOpaqueSurface() failed");
 
-  print_timestamp_time("SDL_DisplayFormat (opaque)");
+  print_timestamp_time("SDLGetNativeSurface (opaque)");
 
   UPDATE_BUSY_STATE();
 
-  /* create native transparent surface for current image */
-  SDL_SetColorKey(sdl_image_tmp, SET_TRANSPARENT_PIXEL,
-                 SDL_MapRGB(sdl_image_tmp->format, 0x00, 0x00, 0x00));
+  // 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();
 
-  /* free temporary surface */
+  // free temporary surface
   SDL_FreeSurface(sdl_image_tmp);
 
   new_bitmap->width = new_bitmap->surface->w;
@@ -2266,9 +2296,9 @@ Bitmap *SDLLoadImage(char *filename)
 }
 
 
-/* ------------------------------------------------------------------------- */
-/* custom cursor fuctions                                                    */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// custom cursor fuctions
+// ----------------------------------------------------------------------------
 
 static SDL_Cursor *create_cursor(struct MouseCursorInfo *cursor_info)
 {
@@ -2284,18 +2314,18 @@ void SDLSetMouseCursor(struct MouseCursorInfo *cursor_info)
   static SDL_Cursor *cursor_default = NULL;
   static SDL_Cursor *cursor_current = NULL;
 
-  /* if invoked for the first time, store the SDL default cursor */
+  // if invoked for the first time, store the SDL default cursor
   if (cursor_default == NULL)
     cursor_default = SDL_GetCursor();
 
-  /* only create new cursor if cursor info (custom only) has changed */
+  // only create new cursor if cursor info (custom only) has changed
   if (cursor_info != NULL && cursor_info != last_cursor_info)
   {
     cursor_current = create_cursor(cursor_info);
     last_cursor_info = cursor_info;
   }
 
-  /* only set new cursor if cursor info (custom or NULL) has changed */
+  // only set new cursor if cursor info (custom or NULL) has changed
   if (cursor_info != last_cursor_info2)
     SDL_SetCursor(cursor_info ? cursor_current : cursor_default);
 
@@ -2303,16 +2333,14 @@ void SDLSetMouseCursor(struct MouseCursorInfo *cursor_info)
 }
 
 
-/* ========================================================================= */
-/* audio functions                                                           */
-/* ========================================================================= */
+// ============================================================================
+// audio functions
+// ============================================================================
 
 void SDLOpenAudio(void)
 {
-#if !defined(TARGET_SDL2)
-  if (!strEqual(setup.system.sdl_audiodriver, ARG_DEFAULT))
-    SDL_putenv(getStringCat2("SDL_AUDIODRIVER=", setup.system.sdl_audiodriver));
-#endif
+  if (program.headless)
+    return;
 
   if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
   {
@@ -2333,7 +2361,7 @@ void SDLOpenAudio(void)
   audio.loops_available = TRUE;
   audio.sound_enabled = TRUE;
 
-  /* set number of available mixer channels */
+  // set number of available mixer channels
   audio.num_channels = Mix_AllocateChannels(NUM_MIXER_CHANNELS);
   audio.music_channel = MUSIC_CHANNEL;
   audio.first_sound_channel = FIRST_SOUND_CHANNEL;
@@ -2351,37 +2379,13 @@ void SDLCloseAudio(void)
 }
 
 
-/* ========================================================================= */
-/* event functions                                                           */
-/* ========================================================================= */
+// ============================================================================
+// event functions
+// ============================================================================
 
-void SDLNextEvent(Event *event)
+void SDLWaitEvent(Event *event)
 {
   SDL_WaitEvent(event);
-
-  if (event->type == EVENT_BUTTONPRESS ||
-      event->type == EVENT_BUTTONRELEASE)
-  {
-    if (((ButtonEvent *)event)->x > video_xoffset)
-      ((ButtonEvent *)event)->x -= video_xoffset;
-    else
-      ((ButtonEvent *)event)->x = 0;
-    if (((ButtonEvent *)event)->y > video_yoffset)
-      ((ButtonEvent *)event)->y -= video_yoffset;
-    else
-      ((ButtonEvent *)event)->y = 0;
-  }
-  else if (event->type == EVENT_MOTIONNOTIFY)
-  {
-    if (((MotionEvent *)event)->x > video_xoffset)
-      ((MotionEvent *)event)->x -= video_xoffset;
-    else
-      ((MotionEvent *)event)->x = 0;
-    if (((MotionEvent *)event)->y > video_yoffset)
-      ((MotionEvent *)event)->y -= video_yoffset;
-    else
-      ((MotionEvent *)event)->y = 0;
-  }
 }
 
 void SDLHandleWindowManagerEvent(Event *event)
@@ -2393,17 +2397,9 @@ void SDLHandleWindowManagerEvent(Event *event)
   SDL_SysWMEvent *syswmevent = (SDL_SysWMEvent *)event;
   SDL_SysWMmsg *syswmmsg = (SDL_SysWMmsg *)(syswmevent->msg);
 
-#if defined(TARGET_SDL2)
   if (syswmmsg->msg.win.msg == WM_DROPFILES)
-#else
-  if (syswmmsg->msg == WM_DROPFILES)
-#endif
   {
-#if defined(TARGET_SDL2)
     HDROP hdrop = (HDROP)syswmmsg->msg.win.wParam;
-#else
-    HDROP hdrop = (HDROP)syswmmsg->wParam;
-#endif
     int i, num_files;
 
     printf("::: SDL_SYSWMEVENT:\n");
@@ -2420,72 +2416,231 @@ void SDLHandleWindowManagerEvent(Event *event)
       printf("::: - '%s'\n", buffer);
     }
 
-#if defined(TARGET_SDL2)
     DragFinish((HDROP)syswmmsg->msg.win.wParam);
-#else
-    DragFinish((HDROP)syswmmsg->wParam);
-#endif
   }
 #endif
 #endif
 }
 
 
-/* ========================================================================= */
-/* joystick functions                                                        */
-/* ========================================================================= */
+// ============================================================================
+// 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} };
+static void *sdl_joystick[MAX_PLAYERS];                // game controller or joystick
+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(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;
 
-  return ((sdl_joystick[nr] = SDL_JoystickOpen(nr)) == NULL ? FALSE : TRUE);
+  sdl_is_controller[nr] = SDL_IsGameController(nr);
+
+#if DEBUG_JOYSTICKS
+  Error(ERR_DEBUG, "opening joystick %d (%s)",
+       nr, (sdl_is_controller[nr] ? "game controller" : "joystick"));
+#endif
+
+  if (sdl_is_controller[nr])
+    sdl_joystick[nr] = SDL_GameControllerOpen(nr);
+  else
+    sdl_joystick[nr] = SDL_JoystickOpen(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;
 
-  SDL_JoystickClose(sdl_joystick[nr]);
+#if DEBUG_JOYSTICKS
+  Error(ERR_DEBUG, "closing joystick %d", nr);
+#endif
+
+  if (sdl_is_controller[nr])
+    SDL_GameControllerClose(sdl_joystick[nr]);
+  else
+    SDL_JoystickClose(sdl_joystick[nr]);
 
   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)
   return (sdl_joystick[nr] != NULL ? TRUE : FALSE);
-#else
-  return (SDL_JoystickOpened(nr) ? TRUE : FALSE);
-#endif
+}
+
+static void setJoystickAxis(int nr, int axis_id_raw, int axis_value)
+{
+  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);
+
+  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)
+{
+  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;
+
+  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)
   {
+    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 DEBUG_JOYSTICKS
+      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 DEBUG_JOYSTICKS
+      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 DEBUG_JOYSTICKS
+      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;
+
     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 DEBUG_JOYSTICKS
+      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 DEBUG_JOYSTICKS
+      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 DEBUG_JOYSTICKS
+      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:
@@ -2493,26 +2648,80 @@ void HandleJoystickEvent(Event *event)
   }
 }
 
-void SDLInitJoysticks()
+void SDLInitJoysticks(void)
 {
   static boolean sdl_joystick_subsystem_initialized = FALSE;
   boolean print_warning = !sdl_joystick_subsystem_initialized;
+  char *mappings_file_base = getPath2(options.conf_directory,
+                                     GAMECONTROLLER_BASENAME);
+  char *mappings_file_user = getPath2(getUserGameDataDir(),
+                                     GAMECONTROLLER_BASENAME);
+  int num_mappings;
   int i;
 
   if (!sdl_joystick_subsystem_initialized)
   {
     sdl_joystick_subsystem_initialized = TRUE;
 
-    if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0)
+    SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
+
+    if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0)
     {
       Error(ERR_EXIT, "SDL_Init() failed: %s", SDL_GetError());
       return;
     }
+
+    num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_base);
+
+    // 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_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;
+
+      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 */
+    // get configured joystick for this player
     char *device_name = setup.input[i].joy.device_name;
     int joystick_nr = getJoystickNrFromDeviceName(device_name);
 
@@ -2524,29 +2733,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;
-
-    /* this allows subsequent calls to 'InitJoysticks' for re-initialization */
-    if (SDLCheckJoystickOpened(joystick_nr))
-      SDLCloseJoystick(joystick_nr);
-
-    if (!setup.input[i].use_joystick)
-      continue;
-
-    if (!SDLOpenJoystick(joystick_nr))
-    {
-      if (print_warning)
-       Error(ERR_WARN, "cannot open joystick %d", joystick_nr);
+    // store configured joystick number for each player
+    joystick.nr[i] = joystick_nr;
+  }
 
-      continue;
-    }
+  // 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(i))
+      SDLCloseJoystick(i);
 
-    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)
@@ -2566,3 +2770,285 @@ 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_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_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 (!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 (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";
+    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);
+
+    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