added support for setting alpha value for global animations
[rocksndiamonds.git] / src / libgame / sdl.c
1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // sdl.c
10 // ============================================================================
11
12 #include "system.h"
13 #include "sound.h"
14 #include "joystick.h"
15 #include "misc.h"
16 #include "setup.h"
17 #include "gadgets.h"
18
19 #define ENABLE_UNUSED_CODE      0       // currently unused functions
20
21 #define DEBUG_JOYSTICKS         0
22
23
24 // ============================================================================
25 // video functions
26 // ============================================================================
27
28 // SDL internal variables
29 static SDL_Window *sdl_window = NULL;
30 static SDL_Renderer *sdl_renderer = NULL;
31 static SDL_Texture *sdl_texture_stream = NULL;
32 static SDL_Texture *sdl_texture_target = NULL;
33 static boolean fullscreen_enabled = FALSE;
34 static boolean limit_screen_updates = FALSE;
35
36
37 // functions from SGE library
38 void sge_Line(SDL_Surface *, Sint16, Sint16, Sint16, Sint16, Uint32);
39
40 #if defined(USE_TOUCH_INPUT_OVERLAY)
41 // functions to draw overlay graphics for touch device input
42 static void DrawTouchInputOverlay(void);
43 static void DrawTouchGadgetsOverlay(void);
44 #endif
45
46 void SDLLimitScreenUpdates(boolean enable)
47 {
48   limit_screen_updates = enable;
49 }
50
51 static void FinalizeScreen(int draw_target)
52 {
53   // copy global animations to render target buffer, if defined (below border)
54   if (gfx.draw_global_anim_function != NULL)
55     gfx.draw_global_anim_function(draw_target, DRAW_GLOBAL_ANIM_STAGE_1);
56
57   // copy global masked border to render target buffer, if defined
58   if (gfx.draw_global_border_function != NULL)
59     gfx.draw_global_border_function(draw_target);
60
61   // copy global animations to render target buffer, if defined (above border)
62   if (gfx.draw_global_anim_function != NULL)
63     gfx.draw_global_anim_function(draw_target, DRAW_GLOBAL_ANIM_STAGE_2);
64
65   // copy tile selection cursor to render target buffer, if defined (above all)
66   if (gfx.draw_tile_cursor_function != NULL)
67     gfx.draw_tile_cursor_function(draw_target);
68
69   // copy envelope request to render target buffer, if needed (above all)
70   if (gfx.draw_envelope_request_function != NULL)
71     gfx.draw_envelope_request_function(draw_target);
72
73   // copy global animations to render target buffer, if defined (mouse pointer)
74   if (gfx.draw_global_anim_function != NULL)
75     gfx.draw_global_anim_function(draw_target, DRAW_GLOBAL_ANIM_STAGE_3);
76 }
77
78 static void UpdateScreenExt(SDL_Rect *rect, boolean with_frame_delay)
79 {
80   if (program.headless)
81     return;
82
83   static DelayCounter update_screen_delay = { 50 };     // (milliseconds)
84   SDL_Surface *screen = backbuffer->surface;
85
86   if (limit_screen_updates &&
87       !DelayReached(&update_screen_delay))
88     return;
89
90   LimitScreenUpdates(FALSE);
91
92 #if 0
93   {
94     static int LastFrameCounter = 0;
95     boolean changed = (FrameCounter != LastFrameCounter);
96
97     Debug("internal:frame", "FrameCounter == %d [%s]", FrameCounter,
98           (changed ? "-" : "SAME FRAME UPDATED"));
99
100     LastFrameCounter = FrameCounter;
101
102     /*
103     if (FrameCounter % 2)
104       return;
105     */
106   }
107 #endif
108
109   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP &&
110       gfx.final_screen_bitmap != NULL)  // may not be initialized yet
111   {
112     // draw global animations using bitmaps instead of using textures
113     // to prevent texture scaling artefacts (this is potentially slower)
114
115     BlitBitmap(backbuffer, gfx.final_screen_bitmap, 0, 0,
116                gfx.win_xsize, gfx.win_ysize, 0, 0);
117
118     FinalizeScreen(DRAW_TO_SCREEN);
119
120     screen = gfx.final_screen_bitmap->surface;
121
122     // force full window redraw
123     rect = NULL;
124   }
125
126   SDL_Texture *sdl_texture = sdl_texture_stream;
127
128   // deactivate use of target texture if render targets are not supported
129   if ((video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
130        video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE) &&
131       sdl_texture_target == NULL)
132     video.screen_rendering_mode = SPECIAL_RENDERING_OFF;
133
134   if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET)
135     sdl_texture = sdl_texture_target;
136
137   if (rect)
138   {
139     int bytes_x = screen->pitch / video.width;
140     int bytes_y = screen->pitch;
141
142     SDL_UpdateTexture(sdl_texture, rect,
143                       screen->pixels + rect->x * bytes_x + rect->y * bytes_y,
144                       screen->pitch);
145   }
146   else
147   {
148     SDL_UpdateTexture(sdl_texture, NULL, screen->pixels, screen->pitch);
149   }
150
151   int xoff = video.screen_xoffset;
152   int yoff = video.screen_yoffset;
153   SDL_Rect dst_rect_screen = { xoff, yoff, video.width, video.height };
154   SDL_Rect *src_rect1 = NULL, *dst_rect1 = NULL;
155   SDL_Rect *src_rect2 = NULL, *dst_rect2 = NULL;
156
157   if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
158       video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
159     dst_rect2 = &dst_rect_screen;
160   else
161     dst_rect1 = &dst_rect_screen;
162
163 #if defined(HAS_SCREEN_KEYBOARD)
164   SDL_Rect src_rect_up = { 0, 0,  video.width, video.height };
165   SDL_Rect dst_rect_up = dst_rect_screen;
166
167   if (video.shifted_up || video.shifted_up_delay.count)
168   {
169     int time_current = SDL_GetTicks();
170     int pos = video.shifted_up_pos;
171     int pos_last = video.shifted_up_pos_last;
172
173     if (!DelayReachedExt(&video.shifted_up_delay, time_current))
174     {
175       int delay_count = time_current - video.shifted_up_delay.count;
176       int delay_value = video.shifted_up_delay.value;
177
178       pos = pos_last + (pos - pos_last) * delay_count / delay_value;
179     }
180     else
181     {
182       video.shifted_up_pos_last = pos;
183       video.shifted_up_delay.count = 0;
184     }
185
186     src_rect_up.y = pos;
187     src_rect_up.h = video.height - pos;
188     dst_rect_up.h = video.height - pos;
189
190     if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
191         video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
192     {
193       src_rect2 = &src_rect_up;
194       dst_rect2 = &dst_rect_up;
195     }
196     else
197     {
198       src_rect1 = &src_rect_up;
199       dst_rect1 = &dst_rect_up;
200     }
201   }
202 #endif
203
204   // clear render target buffer
205   SDL_RenderClear(sdl_renderer);
206
207   // set renderer to use target texture for rendering
208   if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
209       video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
210     SDL_SetRenderTarget(sdl_renderer, sdl_texture_target);
211
212   // copy backbuffer texture to render target buffer
213   if (video.screen_rendering_mode != SPECIAL_RENDERING_TARGET)
214     SDL_RenderCopy(sdl_renderer, sdl_texture_stream, src_rect1, dst_rect1);
215
216   if (video.screen_rendering_mode != SPECIAL_RENDERING_BITMAP)
217     FinalizeScreen(DRAW_TO_SCREEN);
218
219   // when using target texture, copy it to screen buffer
220   if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET ||
221       video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE)
222   {
223     SDL_SetRenderTarget(sdl_renderer, NULL);
224     SDL_RenderCopy(sdl_renderer, sdl_texture_target, src_rect2, dst_rect2);
225   }
226
227 #if defined(USE_TOUCH_INPUT_OVERLAY)
228   // draw overlay graphics for touch device input, if needed
229   DrawTouchInputOverlay();
230
231   // draw overlay gadgets for touch device input, if needed
232   DrawTouchGadgetsOverlay();
233 #endif
234
235   // global synchronization point of the game to align video frame delay
236   if (with_frame_delay)
237     WaitUntilDelayReached(&video.frame_delay);
238
239   video.frame_counter++;
240
241   // show render target buffer on screen
242   SDL_RenderPresent(sdl_renderer);
243 }
244
245 static void UpdateScreen_WithFrameDelay(SDL_Rect *rect)
246 {
247   PumpEvents();         // execute event filter actions while waiting
248
249   UpdateScreenExt(rect, TRUE);
250 }
251
252 static void UpdateScreen_WithoutFrameDelay(SDL_Rect *rect)
253 {
254   UpdateScreenExt(rect, FALSE);
255 }
256
257 void Delay_WithScreenUpdates(unsigned int delay)
258 {
259   unsigned int time_current = SDL_GetTicks();
260   unsigned int time_delayed = time_current + delay;
261
262   while (time_current < time_delayed)
263   {
264     // updating the screen contains waiting for frame delay (non-busy)
265     UpdateScreen_WithFrameDelay(NULL);
266
267     time_current = SDL_GetTicks();
268   }
269 }
270
271 static void SDLSetWindowIcon(char *basename)
272 {
273   // (setting the window icon on Mac OS X would replace the high-quality
274   // dock icon with the currently smaller (and uglier) icon from file)
275
276 #if !defined(PLATFORM_MAC)
277   char *filename = getCustomImageFilename(basename);
278   SDL_Surface *surface;
279
280   if (filename == NULL)
281   {
282     Warn("SDLSetWindowIcon(): cannot find file '%s'", basename);
283
284     return;
285   }
286
287   if ((surface = IMG_Load(filename)) == NULL)
288   {
289     Warn("IMG_Load('%s') failed: %s", basename, SDL_GetError());
290
291     return;
292   }
293
294   // set transparent color
295   SDL_SetColorKey(surface, SET_TRANSPARENT_PIXEL,
296                   SDL_MapRGB(surface->format, 0x00, 0x00, 0x00));
297
298   SDL_SetWindowIcon(sdl_window, surface);
299 #endif
300 }
301
302 static boolean equalSDLPixelFormat(SDL_PixelFormat *format1,
303                                    SDL_PixelFormat *format2)
304 {
305   return (format1->format        == format2->format &&
306           format1->BitsPerPixel  == format2->BitsPerPixel &&
307           format1->BytesPerPixel == format2->BytesPerPixel &&
308           format1->Rmask         == format2->Rmask &&
309           format1->Gmask         == format2->Gmask &&
310           format1->Bmask         == format2->Bmask);
311 }
312
313 static void SDLCopyColorKey(SDL_Surface *src_surface, SDL_Surface *dst_surface)
314 {
315   Pixel color_key;
316   Uint8 r, g, b;
317
318   // check if source surface has a color key
319   if (SDL_GetColorKey(src_surface, &color_key) == 0)
320   {
321     // get RGB values of color key of source surface
322     SDL_GetRGB(color_key, src_surface->format, &r, &g, &b);
323
324     // get color key from RGB values in destination surface format
325     color_key = SDL_MapRGB(dst_surface->format, r, g, b);
326
327     // set color key in destination surface
328     SDL_SetColorKey(dst_surface, SET_TRANSPARENT_PIXEL, color_key);
329   }
330   else
331   {
332     // unset color key in destination surface
333     SDL_SetColorKey(dst_surface, UNSET_TRANSPARENT_PIXEL, 0);
334   }
335 }
336
337 static boolean SDLHasColorKey(SDL_Surface *surface)
338 {
339   Pixel color_key;
340
341   return (SDL_GetColorKey(surface, &color_key) == 0);
342 }
343
344 static boolean SDLHasAlpha(SDL_Surface *surface)
345 {
346   SDL_BlendMode blend_mode;
347
348   if (SDL_GetSurfaceBlendMode(surface, &blend_mode) != 0)
349     return FALSE;
350
351   return (blend_mode == SDL_BLENDMODE_BLEND);
352 }
353
354 static void SDLSetSurfaceAlpha(SDL_Surface *surface, boolean set, int alpha)
355 {
356   SDL_BlendMode blend_mode = (set ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE);
357
358   SDL_SetSurfaceBlendMode(surface, blend_mode);
359   SDL_SetSurfaceAlphaMod(surface, alpha);
360 }
361
362 static void SDLSetTextureAlpha(SDL_Texture *texture, boolean set, int alpha)
363 {
364   SDL_BlendMode blend_mode = (set ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE);
365
366   SDL_SetTextureBlendMode(texture, blend_mode);
367   SDL_SetTextureAlphaMod(texture, alpha);
368 }
369
370 static void SDLSetBitmapAlpha(Bitmap *bitmap, boolean is_texture,
371                               boolean is_masked)
372 {
373   int alpha_next_blit = bitmap->alpha_next_blit;
374
375   // alpha value must be requested every time before blitting, if needed
376   bitmap->alpha_next_blit = -1;
377
378   // nothing to do if requested alpha value is already set
379   if (bitmap->alpha[is_texture][is_masked] == alpha_next_blit)
380     return;
381
382   // store requested alpha value for masked/unmasked surface/texture
383   bitmap->alpha[is_texture][is_masked] = alpha_next_blit;
384
385   // set blend mode if bitmap is masked or if alpha value is defined
386   boolean set_blend_mode = (is_masked || alpha_next_blit != -1);
387
388   // if alpha value is undefined, use default (opaque) alpha value
389   if (alpha_next_blit == -1)
390     alpha_next_blit = SDL_ALPHA_OPAQUE;
391
392   if (is_texture)
393     SDLSetTextureAlpha(is_masked ? bitmap->texture_masked : bitmap->texture,
394                        set_blend_mode, alpha_next_blit);
395   else
396     SDLSetSurfaceAlpha(is_masked ? bitmap->surface_masked : bitmap->surface,
397                        set_blend_mode, alpha_next_blit);
398 }
399
400 void SDLSetAlpha(SDL_Surface *surface, boolean set, int alpha)
401 {
402   SDLSetSurfaceAlpha(surface, set, alpha);
403 }
404
405 const char *SDLGetRendererName(void)
406 {
407   static SDL_RendererInfo renderer_info;
408
409   SDL_GetRendererInfo(sdl_renderer, &renderer_info);
410
411   return renderer_info.name;
412 }
413
414 SDL_Surface *SDLGetNativeSurface(SDL_Surface *surface)
415 {
416   SDL_PixelFormat format;
417   SDL_Surface *new_surface;
418
419   if (surface == NULL)
420     return NULL;
421
422   if (backbuffer && backbuffer->surface)
423   {
424     format = *backbuffer->surface->format;
425     format.Amask = surface->format->Amask;      // keep alpha channel
426   }
427   else
428   {
429     format = *surface->format;
430   }
431
432   new_surface = SDL_ConvertSurface(surface, &format, 0);
433
434   if (new_surface == NULL)
435     Fail("SDL_ConvertSurface() failed: %s", SDL_GetError());
436
437   // workaround for a bug in SDL 2.0.12 (which does not convert the color key)
438   if (SDLHasColorKey(surface) && !SDLHasColorKey(new_surface))
439     SDLCopyColorKey(surface, new_surface);
440
441   return new_surface;
442 }
443
444 boolean SDLSetNativeSurface(SDL_Surface **surface)
445 {
446   SDL_Surface *new_surface;
447
448   if (surface == NULL ||
449       *surface == NULL ||
450       backbuffer == NULL ||
451       backbuffer->surface == NULL)
452     return FALSE;
453
454   // if pixel format already optimized for destination surface, do nothing
455   if (equalSDLPixelFormat((*surface)->format, backbuffer->surface->format))
456     return FALSE;
457
458   new_surface = SDLGetNativeSurface(*surface);
459
460   SDL_FreeSurface(*surface);
461
462   *surface = new_surface;
463
464   return TRUE;
465 }
466
467 static SDL_Texture *SDLCreateTextureFromSurface(SDL_Surface *surface)
468 {
469   if (program.headless)
470     return NULL;
471
472   SDL_Texture *texture = SDL_CreateTextureFromSurface(sdl_renderer, surface);
473
474   if (texture == NULL)
475     Fail("SDL_CreateTextureFromSurface() failed: %s", SDL_GetError());
476
477   return texture;
478 }
479
480 void SDLCreateBitmapTextures(Bitmap *bitmap)
481 {
482   if (bitmap == NULL)
483     return;
484
485   if (bitmap->texture)
486     SDL_DestroyTexture(bitmap->texture);
487   if (bitmap->texture_masked)
488     SDL_DestroyTexture(bitmap->texture_masked);
489
490   bitmap->texture        = SDLCreateTextureFromSurface(bitmap->surface);
491   bitmap->texture_masked = SDLCreateTextureFromSurface(bitmap->surface_masked);
492 }
493
494 void SDLFreeBitmapTextures(Bitmap *bitmap)
495 {
496   if (bitmap == NULL)
497     return;
498
499   if (bitmap->texture)
500     SDL_DestroyTexture(bitmap->texture);
501   if (bitmap->texture_masked)
502     SDL_DestroyTexture(bitmap->texture_masked);
503
504   bitmap->texture = NULL;
505   bitmap->texture_masked = NULL;
506 }
507
508 void SDLInitVideoDisplay(void)
509 {
510   // set hint to select render driver as specified in setup config file
511   if (!strEqual(setup.system.sdl_renderdriver, ARG_DEFAULT))
512     SDL_SetHint(SDL_HINT_RENDER_DRIVER, setup.system.sdl_renderdriver);
513
514   // initialize SDL video
515   if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
516     Fail("SDL_InitSubSystem() failed: %s", SDL_GetError());
517
518   // set default SDL depth
519   video.default_depth = 32;     // (how to determine video depth in SDL2?)
520   //
521   // Code used with SDL 1.2:
522   // video.default_depth = SDL_GetVideoInfo()->vfmt->BitsPerPixel;
523 }
524
525 static void SDLInitVideoBuffer_VideoBuffer(boolean fullscreen)
526 {
527   if (program.headless)
528     return;
529
530   video.window_scaling_percent = setup.window_scaling_percent;
531   video.window_scaling_quality = setup.window_scaling_quality;
532
533   SDLSetScreenRenderingMode(setup.screen_rendering_mode);
534
535   // SDL 2.0: support for (desktop) fullscreen mode available
536   video.fullscreen_available = TRUE;
537
538   // open SDL video output device (window or fullscreen mode)
539   if (!SDLSetVideoMode(fullscreen))
540     Fail("setting video mode failed");
541
542   // !!! SDL2 can only set the window icon if the window already exists !!!
543   // set window icon
544   SDLSetWindowIcon(program.icon_filename);
545
546   // set window and icon title
547   SDLSetWindowTitle();
548 }
549
550 static void SDLInitVideoBuffer_DrawBuffer(void)
551 {
552   /* SDL cannot directly draw to the visible video framebuffer like X11,
553      but always uses a backbuffer, which is then blitted to the visible
554      video framebuffer with 'SDL_UpdateRect' (or replaced with the current
555      visible video framebuffer with 'SDL_Flip', if the hardware supports
556      this). Therefore do not use an additional backbuffer for drawing, but
557      use a symbolic buffer (distinguishable from the SDL backbuffer) called
558      'window', which indicates that the SDL backbuffer should be updated to
559      the visible video framebuffer when attempting to blit to it.
560
561      For convenience, it seems to be a good idea to create this symbolic
562      buffer 'window' at the same size as the SDL backbuffer. Although it
563      should never be drawn to directly, it would do no harm nevertheless. */
564
565   // create additional (symbolic) buffer for double-buffering
566   ReCreateBitmap(&window, video.width, video.height);
567
568   // create dummy drawing buffer for headless mode, if needed
569   if (program.headless)
570     ReCreateBitmap(&backbuffer, video.width, video.height);
571 }
572
573 void SDLInitVideoBuffer(boolean fullscreen)
574 {
575   SDLInitVideoBuffer_VideoBuffer(fullscreen);
576   SDLInitVideoBuffer_DrawBuffer();
577 }
578
579 static boolean SDLCreateScreen(boolean fullscreen)
580 {
581   SDL_Surface *new_surface = NULL;
582
583   int surface_flags_window     = SURFACE_FLAGS;
584   int surface_flags_fullscreen = SURFACE_FLAGS | SDL_WINDOW_FULLSCREEN_DESKTOP;
585
586 #if 1
587   int renderer_flags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE;
588
589   video.vsync_mode = VSYNC_MODE_OFF;
590
591   if (!strEqual(setup.vsync_mode, STR_VSYNC_MODE_OFF))
592   {
593     renderer_flags |= SDL_RENDERER_PRESENTVSYNC;
594     video.vsync_mode = VSYNC_MODE_NORMAL;
595   }
596 #else
597   /* If SDL_CreateRenderer() is called from within a VirtualBox Windows VM
598      _without_ enabling 2D/3D acceleration and/or guest additions installed,
599      it will crash if flags are *not* set to SDL_RENDERER_SOFTWARE (because
600      it will try to use accelerated graphics and apparently fails miserably) */
601   int renderer_flags = SDL_RENDERER_SOFTWARE;
602 #endif
603
604   int width  = video.width;
605   int height = video.height;
606   int screen_width  = video.screen_width;
607   int screen_height = video.screen_height;
608   int surface_flags = (fullscreen ? surface_flags_fullscreen :
609                        surface_flags_window);
610   int display_nr = options.display_nr;
611
612   // default window size is unscaled
613   video.window_width  = screen_width;
614   video.window_height = screen_height;
615
616   // store if initial screen mode is fullscreen mode when changing screen size
617   video.fullscreen_initial = fullscreen;
618
619   float window_scaling_factor = (float)setup.window_scaling_percent / 100;
620
621   video.window_width  = window_scaling_factor * screen_width;
622   video.window_height = window_scaling_factor * screen_height;
623
624   if (sdl_texture_stream)
625   {
626     SDL_DestroyTexture(sdl_texture_stream);
627     sdl_texture_stream = NULL;
628   }
629
630   if (sdl_texture_target)
631   {
632     SDL_DestroyTexture(sdl_texture_target);
633     sdl_texture_target = NULL;
634   }
635
636   if (!(fullscreen && fullscreen_enabled))
637   {
638     if (sdl_renderer)
639     {
640       SDL_DestroyRenderer(sdl_renderer);
641       sdl_renderer = NULL;
642     }
643
644     if (sdl_window)
645     {
646       SDL_SetWindowSize(sdl_window, video.window_width, video.window_height);
647     }
648   }
649
650   if (sdl_window == NULL)
651     sdl_window = SDL_CreateWindow(program.window_title,
652                                   SDL_WINDOWPOS_CENTERED_DISPLAY(display_nr),
653                                   SDL_WINDOWPOS_CENTERED_DISPLAY(display_nr),
654                                   video.window_width,
655                                   video.window_height,
656                                   surface_flags);
657
658   if (sdl_window != NULL)
659   {
660     if (sdl_renderer == NULL)
661       sdl_renderer = SDL_CreateRenderer(sdl_window, -1, renderer_flags);
662
663     if (sdl_renderer != NULL)
664     {
665       // SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
666       SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, setup.window_scaling_quality);
667
668       // required for setting adaptive vsync when using OpenGL renderer
669       SDLSetScreenVsyncMode(setup.vsync_mode);
670
671       sdl_texture_stream = SDL_CreateTexture(sdl_renderer,
672                                              SDL_PIXELFORMAT_ARGB8888,
673                                              SDL_TEXTUREACCESS_STREAMING,
674                                              width, height);
675
676       if (SDL_RenderTargetSupported(sdl_renderer))
677         sdl_texture_target = SDL_CreateTexture(sdl_renderer,
678                                                SDL_PIXELFORMAT_ARGB8888,
679                                                SDL_TEXTUREACCESS_TARGET,
680                                                width, height);
681
682       if (sdl_texture_stream != NULL)
683       {
684         // use SDL default values for RGB masks and no alpha channel
685         new_surface = SDL_CreateRGBSurface(0, width, height, 32, 0,0,0, 0);
686
687         if (new_surface == NULL)
688           Warn("SDL_CreateRGBSurface() failed: %s", SDL_GetError());
689       }
690       else
691       {
692         Warn("SDL_CreateTexture() failed: %s", SDL_GetError());
693       }
694     }
695     else
696     {
697       Warn("SDL_CreateRenderer() failed: %s", SDL_GetError());
698     }
699   }
700   else
701   {
702     Warn("SDL_CreateWindow() failed: %s", SDL_GetError());
703   }
704
705   SDLSetScreenProperties();
706
707   // store fullscreen state ("video.fullscreen_enabled" may not reflect this!)
708   if (new_surface != NULL)
709     fullscreen_enabled = fullscreen;
710
711   if (backbuffer == NULL)
712     backbuffer = CreateBitmapStruct();
713
714   backbuffer->width  = video.width;
715   backbuffer->height = video.height;
716
717   if (backbuffer->surface)
718     SDL_FreeSurface(backbuffer->surface);
719
720   backbuffer->surface = new_surface;
721
722   return (new_surface != NULL);
723 }
724
725 boolean SDLSetVideoMode(boolean fullscreen)
726 {
727   boolean success = FALSE;
728
729   SetWindowTitle();
730
731   if (fullscreen && !video.fullscreen_enabled && video.fullscreen_available)
732   {
733     // switch display to fullscreen mode, if available
734     success = SDLCreateScreen(TRUE);
735
736     if (!success)
737     {
738       // switching display to fullscreen mode failed -- do not try it again
739       video.fullscreen_available = FALSE;
740     }
741     else
742     {
743       video.fullscreen_enabled = TRUE;
744     }
745   }
746
747   if ((!fullscreen && video.fullscreen_enabled) || !success)
748   {
749     // switch display to window mode
750     success = SDLCreateScreen(FALSE);
751
752     if (!success)
753     {
754       // switching display to window mode failed -- should not happen
755     }
756     else
757     {
758       video.fullscreen_enabled = FALSE;
759       video.window_scaling_percent = setup.window_scaling_percent;
760       video.window_scaling_quality = setup.window_scaling_quality;
761
762       SDLSetScreenRenderingMode(setup.screen_rendering_mode);
763     }
764   }
765
766   SDLRedrawWindow();                    // map window
767
768   return success;
769 }
770
771 void SDLSetWindowTitle(void)
772 {
773   if (sdl_window == NULL)
774     return;
775
776   SDL_SetWindowTitle(sdl_window, program.window_title);
777 }
778
779 void SDLSetWindowScaling(int window_scaling_percent)
780 {
781   if (sdl_window == NULL)
782     return;
783
784   float window_scaling_factor = (float)window_scaling_percent / 100;
785   int new_window_width  = (int)(window_scaling_factor * video.screen_width);
786   int new_window_height = (int)(window_scaling_factor * video.screen_height);
787
788   SDL_SetWindowSize(sdl_window, new_window_width, new_window_height);
789
790   video.window_scaling_percent = window_scaling_percent;
791   video.window_width  = new_window_width;
792   video.window_height = new_window_height;
793
794   SetWindowTitle();
795 }
796
797 void SDLSetWindowScalingQuality(char *window_scaling_quality)
798 {
799   SDL_Texture *new_texture;
800
801   if (sdl_texture_stream == NULL)
802     return;
803
804   SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, window_scaling_quality);
805
806   new_texture = SDL_CreateTexture(sdl_renderer,
807                                   SDL_PIXELFORMAT_ARGB8888,
808                                   SDL_TEXTUREACCESS_STREAMING,
809                                   video.width, video.height);
810
811   if (new_texture != NULL)
812   {
813     SDL_DestroyTexture(sdl_texture_stream);
814
815     sdl_texture_stream = new_texture;
816   }
817
818   if (SDL_RenderTargetSupported(sdl_renderer))
819     new_texture = SDL_CreateTexture(sdl_renderer,
820                                     SDL_PIXELFORMAT_ARGB8888,
821                                     SDL_TEXTUREACCESS_TARGET,
822                                     video.width, video.height);
823   else
824     new_texture = NULL;
825
826   if (new_texture != NULL)
827   {
828     SDL_DestroyTexture(sdl_texture_target);
829
830     sdl_texture_target = new_texture;
831   }
832
833   SDLRedrawWindow();
834
835   video.window_scaling_quality = window_scaling_quality;
836 }
837
838 void SDLSetWindowFullscreen(boolean fullscreen)
839 {
840   if (sdl_window == NULL)
841     return;
842
843   int flags = (fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
844
845   if (SDL_SetWindowFullscreen(sdl_window, flags) == 0)
846     video.fullscreen_enabled = fullscreen_enabled = fullscreen;
847
848   // if screen size was changed in fullscreen mode, correct desktop window size
849   if (!fullscreen && video.fullscreen_initial)
850   {
851     SDLSetWindowScaling(setup.window_scaling_percent);
852     SDL_SetWindowPosition(sdl_window,
853                           SDL_WINDOWPOS_CENTERED_DISPLAY(options.display_nr),
854                           SDL_WINDOWPOS_CENTERED_DISPLAY(options.display_nr));
855
856     video.fullscreen_initial = FALSE;
857   }
858 }
859
860 void SDLSetDisplaySize(void)
861 {
862   if (sdl_renderer != NULL)
863   {
864     int w, h;
865
866     SDL_GetRendererOutputSize(sdl_renderer, &w, &h);
867
868     video.display_width  = w;
869     video.display_height = h;
870
871 #if 0
872     Debug("video", "SDL renderer size: %d x %d",
873           video.display_width, video.display_height);
874 #endif
875   }
876   else
877   {
878     SDL_Rect display_bounds;
879
880     SDL_GetDisplayBounds(0, &display_bounds);
881
882     video.display_width  = display_bounds.w;
883     video.display_height = display_bounds.h;
884
885 #if 0
886     Debug("video", "SDL display size: %d x %d",
887           video.display_width, video.display_height);
888 #endif
889   }
890 }
891
892 void SDLSetScreenSizeAndOffsets(int width, int height)
893 {
894   // set default video screen size and offsets
895   video.screen_width = width;
896   video.screen_height = height;
897   video.screen_xoffset = 0;
898   video.screen_yoffset = 0;
899
900 #if defined(USE_COMPLETE_DISPLAY)
901   float ratio_video   = (float) width / height;
902   float ratio_display = (float) video.display_width / video.display_height;
903
904   if (ratio_video != ratio_display)
905   {
906     // adjust drawable screen size to cover the whole device display
907
908     if (ratio_video < ratio_display)
909       video.screen_width  *= ratio_display / ratio_video;
910     else
911       video.screen_height *= ratio_video / ratio_display;
912
913     video.screen_xoffset = (video.screen_width  - width)  / 2;
914     video.screen_yoffset = (video.screen_height - height) / 2;
915
916 #if 0
917     Debug("video", "Changing screen from %dx%d to %dx%d (%.2f to %.2f)",
918           width, height,
919           video.screen_width, video.screen_height,
920           ratio_video, ratio_display);
921 #endif
922   }
923 #endif
924 }
925
926 void SDLSetScreenSizeForRenderer(int width, int height)
927 {
928   SDL_RenderSetLogicalSize(sdl_renderer, width, height);
929 }
930
931 void SDLSetScreenProperties(void)
932 {
933   SDLSetDisplaySize();
934   SDLSetScreenSizeAndOffsets(video.width, video.height);
935   SDLSetScreenSizeForRenderer(video.screen_width, video.screen_height);
936
937   SetOverlayGridSizeAndButtons();
938 }
939
940 void SDLSetScreenRenderingMode(char *screen_rendering_mode)
941 {
942   video.screen_rendering_mode =
943     (strEqual(screen_rendering_mode, STR_SPECIAL_RENDERING_BITMAP) ?
944      SPECIAL_RENDERING_BITMAP :
945      strEqual(screen_rendering_mode, STR_SPECIAL_RENDERING_TARGET) ?
946      SPECIAL_RENDERING_TARGET:
947      strEqual(screen_rendering_mode, STR_SPECIAL_RENDERING_DOUBLE) ?
948      SPECIAL_RENDERING_DOUBLE : SPECIAL_RENDERING_OFF);
949 }
950
951 void SDLSetScreenVsyncMode(char *vsync_mode)
952 {
953   // changing vsync mode without re-creating renderer only supported by OpenGL
954   if (!strPrefixLower((char *)SDLGetRendererName(), "opengl"))
955     return;
956
957   int interval = VSYNC_MODE_STR_TO_INT(vsync_mode);
958   int result = SDL_GL_SetSwapInterval(interval);
959
960   // if adaptive vsync requested, but not supported, retry with normal vsync
961   if (result == -1 && interval == VSYNC_MODE_ADAPTIVE)
962   {
963     interval = VSYNC_MODE_NORMAL;
964
965     result = SDL_GL_SetSwapInterval(interval);
966   }
967
968   if (result == -1)
969     interval = VSYNC_MODE_OFF;
970
971   video.vsync_mode = interval;
972 }
973
974 void SDLRedrawWindow(void)
975 {
976   UpdateScreen_WithoutFrameDelay(NULL);
977 }
978
979 void SDLCreateBitmapContent(Bitmap *bitmap, int width, int height,
980                             int depth)
981 {
982   if (program.headless)
983     return;
984
985   SDL_Surface *surface =
986     SDL_CreateRGBSurface(SURFACE_FLAGS, width, height, depth, 0,0,0, 0);
987
988   if (surface == NULL)
989     Fail("SDL_CreateRGBSurface() failed: %s", SDL_GetError());
990
991   SDLSetNativeSurface(&surface);
992
993   bitmap->surface = surface;
994 }
995
996 void SDLFreeBitmapPointers(Bitmap *bitmap)
997 {
998   if (bitmap->surface)
999     SDL_FreeSurface(bitmap->surface);
1000   if (bitmap->surface_masked)
1001     SDL_FreeSurface(bitmap->surface_masked);
1002
1003   bitmap->surface = NULL;
1004   bitmap->surface_masked = NULL;
1005
1006   if (bitmap->texture)
1007     SDL_DestroyTexture(bitmap->texture);
1008   if (bitmap->texture_masked)
1009     SDL_DestroyTexture(bitmap->texture_masked);
1010
1011   bitmap->texture = NULL;
1012   bitmap->texture_masked = NULL;
1013 }
1014
1015 void SDLCopyArea(Bitmap *src_bitmap, Bitmap *dst_bitmap,
1016                  int src_x, int src_y, int width, int height,
1017                  int dst_x, int dst_y, int mask_mode)
1018 {
1019   Bitmap *real_dst_bitmap = (dst_bitmap == window ? backbuffer : dst_bitmap);
1020   SDL_Rect src_rect, dst_rect;
1021
1022   src_rect.x = src_x;
1023   src_rect.y = src_y;
1024   src_rect.w = width;
1025   src_rect.h = height;
1026
1027   dst_rect.x = dst_x;
1028   dst_rect.y = dst_y;
1029   dst_rect.w = width;
1030   dst_rect.h = height;
1031
1032   SDLSetBitmapAlpha(src_bitmap, FALSE, mask_mode == BLIT_MASKED);
1033
1034   // if (src_bitmap != backbuffer || dst_bitmap != window)
1035   if (!(src_bitmap == backbuffer && dst_bitmap == window))
1036     SDL_BlitSurface((mask_mode == BLIT_MASKED ?
1037                      src_bitmap->surface_masked : src_bitmap->surface),
1038                     &src_rect, real_dst_bitmap->surface, &dst_rect);
1039
1040   if (dst_bitmap == window)
1041     UpdateScreen_WithFrameDelay(&dst_rect);
1042 }
1043
1044 void SDLBlitTexture(Bitmap *bitmap,
1045                     int src_x, int src_y, int width, int height,
1046                     int dst_x, int dst_y, int mask_mode)
1047 {
1048   SDL_Texture *texture;
1049   SDL_Rect src_rect;
1050   SDL_Rect dst_rect;
1051
1052   texture =
1053     (mask_mode == BLIT_MASKED ? bitmap->texture_masked : bitmap->texture);
1054
1055   if (texture == NULL)
1056     return;
1057
1058   src_rect.x = src_x;
1059   src_rect.y = src_y;
1060   src_rect.w = width;
1061   src_rect.h = height;
1062
1063   dst_rect.x = dst_x;
1064   dst_rect.y = dst_y;
1065   dst_rect.w = width;
1066   dst_rect.h = height;
1067
1068   SDLSetBitmapAlpha(bitmap, TRUE, mask_mode == BLIT_MASKED);
1069
1070   SDL_RenderCopy(sdl_renderer, texture, &src_rect, &dst_rect);
1071 }
1072
1073 void SDLFillRectangle(Bitmap *dst_bitmap, int x, int y, int width, int height,
1074                       Uint32 color)
1075 {
1076   Bitmap *real_dst_bitmap = (dst_bitmap == window ? backbuffer : dst_bitmap);
1077   SDL_Rect rect;
1078
1079   rect.x = x;
1080   rect.y = y;
1081   rect.w = width;
1082   rect.h = height;
1083
1084   SDL_FillRect(real_dst_bitmap->surface, &rect, color);
1085
1086   if (dst_bitmap == window)
1087     UpdateScreen_WithFrameDelay(&rect);
1088 }
1089
1090 void PrepareFadeBitmap(int draw_target)
1091 {
1092   Bitmap *fade_bitmap =
1093     (draw_target == DRAW_TO_FADE_SOURCE ? gfx.fade_bitmap_source :
1094      draw_target == DRAW_TO_FADE_TARGET ? gfx.fade_bitmap_target : NULL);
1095
1096   if (fade_bitmap == NULL)
1097     return;
1098
1099   // copy backbuffer to fading buffer
1100   BlitBitmap(backbuffer, fade_bitmap, 0, 0, gfx.win_xsize, gfx.win_ysize, 0, 0);
1101
1102   // add border and animations to fading buffer
1103   FinalizeScreen(draw_target);
1104 }
1105
1106 void SDLFadeRectangle(int x, int y, int width, int height,
1107                       int fade_mode, int fade_delay, int post_delay,
1108                       void (*draw_border_function)(void))
1109 {
1110   SDL_Surface *surface_backup = gfx.fade_bitmap_backup->surface;
1111   SDL_Surface *surface_source = gfx.fade_bitmap_source->surface;
1112   SDL_Surface *surface_target = gfx.fade_bitmap_target->surface;
1113   SDL_Surface *surface_black  = gfx.fade_bitmap_black->surface;
1114   SDL_Surface *surface_screen = backbuffer->surface;
1115   SDL_Rect src_rect, dst_rect;
1116   SDL_Rect dst_rect2;
1117   int src_x = x, src_y = y;
1118   int dst_x = x, dst_y = y;
1119   unsigned int time_last, time_current;
1120
1121   // store function for drawing global masked border
1122   void (*draw_global_border_function)(int) = gfx.draw_global_border_function;
1123
1124   // deactivate drawing of global border while fading, if needed
1125   if (draw_border_function == NULL)
1126     gfx.draw_global_border_function = NULL;
1127
1128   src_rect.x = src_x;
1129   src_rect.y = src_y;
1130   src_rect.w = width;
1131   src_rect.h = height;
1132
1133   dst_rect.x = dst_x;
1134   dst_rect.y = dst_y;
1135   dst_rect.w = width;           // (ignored)
1136   dst_rect.h = height;          // (ignored)
1137
1138   dst_rect2 = dst_rect;
1139
1140   // before fading in, store backbuffer (without animation graphics)
1141   if (fade_mode & (FADE_TYPE_FADE_IN | FADE_TYPE_TRANSFORM))
1142     SDL_BlitSurface(surface_screen, &dst_rect, surface_backup, &src_rect);
1143
1144   // copy source and target surfaces to temporary surfaces for fading
1145   if (fade_mode & FADE_TYPE_TRANSFORM)
1146   {
1147     // (source and target fading buffer already prepared)
1148   }
1149   else if (fade_mode & FADE_TYPE_FADE_IN)
1150   {
1151     // (target fading buffer already prepared)
1152     SDL_BlitSurface(surface_black,  &src_rect, surface_source, &src_rect);
1153   }
1154   else          // FADE_TYPE_FADE_OUT
1155   {
1156     // (source fading buffer already prepared)
1157     SDL_BlitSurface(surface_black,  &src_rect, surface_target, &src_rect);
1158   }
1159
1160   time_current = SDL_GetTicks();
1161
1162   if (fade_delay <= 0)
1163   {
1164     // immediately draw final target frame without delay
1165     fade_mode &= (FADE_MODE_FADE | FADE_MODE_TRANSFORM);
1166     fade_delay = 1;
1167     time_current -= 1;
1168
1169     // when fading without delay, also skip post delay
1170     post_delay = 0;
1171   }
1172
1173   if (fade_mode == FADE_MODE_MELT)
1174   {
1175     boolean done = FALSE;
1176     int melt_pixels = 2;
1177     int melt_columns = width / melt_pixels;
1178     int ypos[melt_columns];
1179     int max_steps = height / 8 + 32;
1180     int steps_done = 0;
1181     float steps = 0;
1182     int i;
1183
1184     SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
1185
1186     SDLSetAlpha(surface_target, FALSE, 0);      // disable alpha blending
1187
1188     ypos[0] = -GetSimpleRandom(16);
1189
1190     for (i = 1 ; i < melt_columns; i++)
1191     {
1192       int r = GetSimpleRandom(3) - 1;   // randomly choose from { -1, 0, -1 }
1193
1194       ypos[i] = ypos[i - 1] + r;
1195
1196       if (ypos[i] > 0)
1197         ypos[i] = 0;
1198       else
1199         if (ypos[i] == -16)
1200           ypos[i] = -15;
1201     }
1202
1203     while (!done)
1204     {
1205       int steps_final;
1206
1207       time_last = time_current;
1208       time_current = SDL_GetTicks();
1209       steps += max_steps * ((float)(time_current - time_last) / fade_delay);
1210       steps_final = MIN(MAX(0, steps), max_steps);
1211
1212       steps_done++;
1213
1214       done = (steps_done >= steps_final);
1215
1216       for (i = 0 ; i < melt_columns; i++)
1217       {
1218         if (ypos[i] < 0)
1219         {
1220           ypos[i]++;
1221
1222           done = FALSE;
1223         }
1224         else if (ypos[i] < height)
1225         {
1226           int y1 = 16;
1227           int y2 = 8;
1228           int y3 = 8;
1229           int dy = (ypos[i] < y1) ? ypos[i] + 1 : y2 + GetSimpleRandom(y3);
1230
1231           if (ypos[i] + dy >= height)
1232             dy = height - ypos[i];
1233
1234           // copy part of (appearing) target surface to upper area
1235           src_rect.x = src_x + i * melt_pixels;
1236           // src_rect.y = src_y + ypos[i];
1237           src_rect.y = src_y;
1238           src_rect.w = melt_pixels;
1239           // src_rect.h = dy;
1240           src_rect.h = ypos[i] + dy;
1241
1242           dst_rect.x = dst_x + i * melt_pixels;
1243           // dst_rect.y = dst_y + ypos[i];
1244           dst_rect.y = dst_y;
1245
1246           if (steps_done >= steps_final)
1247             SDL_BlitSurface(surface_target, &src_rect,
1248                             surface_screen, &dst_rect);
1249
1250           ypos[i] += dy;
1251
1252           // copy part of (disappearing) source surface to lower area
1253           src_rect.x = src_x + i * melt_pixels;
1254           src_rect.y = src_y;
1255           src_rect.w = melt_pixels;
1256           src_rect.h = height - ypos[i];
1257
1258           dst_rect.x = dst_x + i * melt_pixels;
1259           dst_rect.y = dst_y + ypos[i];
1260
1261           if (steps_done >= steps_final)
1262             SDL_BlitSurface(surface_source, &src_rect,
1263                             surface_screen, &dst_rect);
1264
1265           done = FALSE;
1266         }
1267         else
1268         {
1269           src_rect.x = src_x + i * melt_pixels;
1270           src_rect.y = src_y;
1271           src_rect.w = melt_pixels;
1272           src_rect.h = height;
1273
1274           dst_rect.x = dst_x + i * melt_pixels;
1275           dst_rect.y = dst_y;
1276
1277           if (steps_done >= steps_final)
1278             SDL_BlitSurface(surface_target, &src_rect,
1279                             surface_screen, &dst_rect);
1280         }
1281       }
1282
1283       if (steps_done >= steps_final)
1284       {
1285         if (draw_border_function != NULL)
1286           draw_border_function();
1287
1288         UpdateScreen_WithFrameDelay(&dst_rect2);
1289
1290         if (PendingEscapeKeyEvent())
1291           break;
1292       }
1293     }
1294   }
1295   else if (fade_mode == FADE_MODE_CURTAIN)
1296   {
1297     float xx;
1298     int xx_final;
1299     int xx_size = width / 2;
1300
1301     SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
1302
1303     SDLSetAlpha(surface_source, FALSE, 0);      // disable alpha blending
1304
1305     for (xx = 0; xx < xx_size;)
1306     {
1307       time_last = time_current;
1308       time_current = SDL_GetTicks();
1309       xx += xx_size * ((float)(time_current - time_last) / fade_delay);
1310       xx_final = MIN(MAX(0, xx), xx_size);
1311
1312       src_rect.x = src_x;
1313       src_rect.y = src_y;
1314       src_rect.w = width;
1315       src_rect.h = height;
1316
1317       dst_rect.x = dst_x;
1318       dst_rect.y = dst_y;
1319
1320       // draw new (target) image to screen buffer
1321       SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
1322
1323       if (xx_final < xx_size)
1324       {
1325         src_rect.w = xx_size - xx_final;
1326         src_rect.h = height;
1327
1328         // draw old (source) image to screen buffer (left side)
1329
1330         src_rect.x = src_x + xx_final;
1331         dst_rect.x = dst_x;
1332
1333         SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
1334
1335         // draw old (source) image to screen buffer (right side)
1336
1337         src_rect.x = src_x + xx_size;
1338         dst_rect.x = dst_x + xx_size + xx_final;
1339
1340         SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
1341       }
1342
1343       if (draw_border_function != NULL)
1344         draw_border_function();
1345
1346       // only update the region of the screen that is affected from fading
1347       UpdateScreen_WithFrameDelay(&dst_rect2);
1348
1349       if (PendingEscapeKeyEvent())
1350         break;
1351     }
1352   }
1353   else          // fading in, fading out or cross-fading
1354   {
1355     float alpha;
1356     int alpha_final;
1357
1358     for (alpha = 0.0; alpha < 255.0;)
1359     {
1360       time_last = time_current;
1361       time_current = SDL_GetTicks();
1362       alpha += 255 * ((float)(time_current - time_last) / fade_delay);
1363       alpha_final = MIN(MAX(0, alpha), 255);
1364
1365       // draw existing (source) image to screen buffer
1366       SDL_BlitSurface(surface_source, &src_rect, surface_screen, &dst_rect);
1367
1368       // draw new (target) image to screen buffer using alpha blending
1369       SDLSetAlpha(surface_target, TRUE, alpha_final);
1370       SDL_BlitSurface(surface_target, &src_rect, surface_screen, &dst_rect);
1371
1372       if (draw_border_function != NULL)
1373         draw_border_function();
1374
1375       // only update the region of the screen that is affected from fading
1376       UpdateScreen_WithFrameDelay(&dst_rect);
1377
1378       if (PendingEscapeKeyEvent())
1379         break;
1380     }
1381   }
1382
1383   if (post_delay > 0)
1384     Delay_WithScreenUpdates(post_delay);
1385
1386   // restore function for drawing global masked border
1387   gfx.draw_global_border_function = draw_global_border_function;
1388
1389   // after fading in, restore backbuffer (without animation graphics)
1390   if (fade_mode & (FADE_TYPE_FADE_IN | FADE_TYPE_TRANSFORM))
1391     SDL_BlitSurface(surface_backup, &dst_rect, surface_screen, &src_rect);
1392 }
1393
1394 void SDLDrawSimpleLine(Bitmap *dst_bitmap, int from_x, int from_y,
1395                        int to_x, int to_y, Uint32 color)
1396 {
1397   SDL_Surface *surface = dst_bitmap->surface;
1398   SDL_Rect rect;
1399
1400   if (from_x > to_x)
1401     swap_numbers(&from_x, &to_x);
1402
1403   if (from_y > to_y)
1404     swap_numbers(&from_y, &to_y);
1405
1406   rect.x = from_x;
1407   rect.y = from_y;
1408   rect.w = (to_x - from_x + 1);
1409   rect.h = (to_y - from_y + 1);
1410
1411   SDL_FillRect(surface, &rect, color);
1412 }
1413
1414 void SDLDrawLine(Bitmap *dst_bitmap, int from_x, int from_y,
1415                  int to_x, int to_y, Uint32 color)
1416 {
1417   sge_Line(dst_bitmap->surface, from_x, from_y, to_x, to_y, color);
1418 }
1419
1420 #if ENABLE_UNUSED_CODE
1421 void SDLDrawLines(SDL_Surface *surface, struct XY *points,
1422                   int num_points, Uint32 color)
1423 {
1424   int i, x, y;
1425   int line_width = 4;
1426
1427   for (i = 0; i < num_points - 1; i++)
1428   {
1429     for (x = 0; x < line_width; x++)
1430     {
1431       for (y = 0; y < line_width; y++)
1432       {
1433         int dx = x - line_width / 2;
1434         int dy = y - line_width / 2;
1435
1436         if ((x == 0 && y == 0) ||
1437             (x == 0 && y == line_width - 1) ||
1438             (x == line_width - 1 && y == 0) ||
1439             (x == line_width - 1 && y == line_width - 1))
1440           continue;
1441
1442         sge_Line(surface, points[i].x + dx, points[i].y + dy,
1443                  points[i+1].x + dx, points[i+1].y + dy, color);
1444       }
1445     }
1446   }
1447 }
1448 #endif
1449
1450 Pixel SDLGetPixel(Bitmap *src_bitmap, int x, int y)
1451 {
1452   SDL_Surface *surface = src_bitmap->surface;
1453
1454   switch (surface->format->BytesPerPixel)
1455   {
1456     case 1:             // assuming 8-bpp
1457     {
1458       return *((Uint8 *)surface->pixels + y * surface->pitch + x);
1459     }
1460     break;
1461
1462     case 2:             // probably 15-bpp or 16-bpp
1463     {
1464       return *((Uint16 *)surface->pixels + y * surface->pitch / 2 + x);
1465     }
1466     break;
1467
1468   case 3:               // slow 24-bpp mode; usually not used
1469     {
1470       // does this work?
1471       Uint8 *pix = (Uint8 *)surface->pixels + y * surface->pitch + x * 3;
1472       Uint32 color = 0;
1473       int shift;
1474
1475       shift = surface->format->Rshift;
1476       color |= *(pix + shift / 8) >> shift;
1477       shift = surface->format->Gshift;
1478       color |= *(pix + shift / 8) >> shift;
1479       shift = surface->format->Bshift;
1480       color |= *(pix + shift / 8) >> shift;
1481
1482       return color;
1483     }
1484     break;
1485
1486   case 4:               // probably 32-bpp
1487     {
1488       return *((Uint32 *)surface->pixels + y * surface->pitch / 4 + x);
1489     }
1490     break;
1491   }
1492
1493   return 0;
1494 }
1495
1496
1497 // ============================================================================
1498 // The following functions were taken from the SGE library
1499 // (SDL Graphics Extension Library) by Anders Lindström
1500 // http://www.etek.chalmers.se/~e8cal1/sge/index.html
1501 // ============================================================================
1502
1503 static void _PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1504 {
1505   if (x >= 0 && x <= surface->w - 1 && y >= 0 && y <= surface->h - 1)
1506   {
1507     switch (surface->format->BytesPerPixel)
1508     {
1509       case 1:
1510       {
1511         // Assuming 8-bpp
1512         *((Uint8 *)surface->pixels + y * surface->pitch + x) = color;
1513       }
1514       break;
1515
1516       case 2:
1517       {
1518         // Probably 15-bpp or 16-bpp
1519         *((Uint16 *)surface->pixels + y * surface->pitch / 2 + x) = color;
1520       }
1521       break;
1522
1523       case 3:
1524       {
1525         // Slow 24-bpp mode, usually not used
1526         Uint8 *pix;
1527         int shift;
1528
1529         // Gack - slow, but endian correct
1530         pix = (Uint8 *)surface->pixels + y * surface->pitch + x * 3;
1531         shift = surface->format->Rshift;
1532         *(pix + shift / 8) = color>>shift;
1533         shift = surface->format->Gshift;
1534         *(pix + shift / 8) = color>>shift;
1535         shift = surface->format->Bshift;
1536         *(pix + shift / 8) = color>>shift;
1537       }
1538       break;
1539
1540       case 4:
1541       {
1542         // Probably 32-bpp
1543         *((Uint32 *)surface->pixels + y * surface->pitch / 4 + x) = color;
1544       }
1545       break;
1546     }
1547   }
1548 }
1549
1550 #if 0
1551 static void _PutPixelRGB(SDL_Surface *surface, Sint16 x, Sint16 y,
1552                          Uint8 R, Uint8 G, Uint8 B)
1553 {
1554   _PutPixel(surface, x, y, SDL_MapRGB(surface->format, R, G, B));
1555 }
1556
1557 static void _PutPixel8(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1558 {
1559   *((Uint8 *)surface->pixels + y * surface->pitch + x) = color;
1560 }
1561
1562 static void _PutPixel16(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1563 {
1564   *((Uint16 *)surface->pixels + y * surface->pitch / 2 + x) = color;
1565 }
1566
1567 static void _PutPixel24(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1568 {
1569   Uint8 *pix;
1570   int shift;
1571
1572   // Gack - slow, but endian correct
1573   pix = (Uint8 *)surface->pixels + y * surface->pitch + x * 3;
1574   shift = surface->format->Rshift;
1575   *(pix + shift / 8) = color>>shift;
1576   shift = surface->format->Gshift;
1577   *(pix + shift / 8) = color>>shift;
1578   shift = surface->format->Bshift;
1579   *(pix + shift / 8) = color>>shift;
1580 }
1581
1582 static void _PutPixel32(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1583 {
1584   *((Uint32 *)surface->pixels + y * surface->pitch / 4 + x) = color;
1585 }
1586
1587 static void _PutPixelX(SDL_Surface *dest, Sint16 x, Sint16 y, Uint32 color)
1588 {
1589   switch (dest->format->BytesPerPixel)
1590   {
1591     case 1:
1592       *((Uint8 *)dest->pixels + y * dest->pitch + x) = color;
1593       break;
1594
1595     case 2:
1596       *((Uint16 *)dest->pixels + y * dest->pitch / 2 + x) = color;
1597       break;
1598
1599     case 3:
1600       _PutPixel24(dest, x, y, color);
1601       break;
1602
1603     case 4:
1604       *((Uint32 *)dest->pixels + y * dest->pitch / 4 + x) = color;
1605       break;
1606   }
1607 }
1608 #endif
1609
1610 static void sge_PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color)
1611 {
1612   if (SDL_MUSTLOCK(surface))
1613   {
1614     if (SDL_LockSurface(surface) < 0)
1615     {
1616       return;
1617     }
1618   }
1619
1620   _PutPixel(surface, x, y, color);
1621
1622   if (SDL_MUSTLOCK(surface))
1623   {
1624     SDL_UnlockSurface(surface);
1625   }
1626 }
1627
1628 #if 0
1629 static void sge_PutPixelRGB(SDL_Surface *surface, Sint16 x, Sint16 y,
1630                             Uint8 r, Uint8 g, Uint8 b)
1631 {
1632   sge_PutPixel(surface, x, y, SDL_MapRGB(surface->format, r, g, b));
1633 }
1634
1635 static Sint32 sge_CalcYPitch(SDL_Surface *dest, Sint16 y)
1636 {
1637   if (y >= 0 && y <= dest->h - 1)
1638   {
1639     switch (dest->format->BytesPerPixel)
1640     {
1641       case 1:
1642         return y * dest->pitch;
1643         break;
1644
1645       case 2:
1646         return y * dest->pitch / 2;
1647         break;
1648
1649       case 3:
1650         return y * dest->pitch;
1651         break;
1652
1653       case 4:
1654         return y * dest->pitch / 4;
1655         break;
1656     }
1657   }
1658
1659   return -1;
1660 }
1661
1662 static void sge_pPutPixel(SDL_Surface *surface, Sint16 x, Sint32 ypitch,
1663                           Uint32 color)
1664 {
1665   if (x >= 0 && x <= surface->w - 1 && ypitch >= 0)
1666   {
1667     switch (surface->format->BytesPerPixel)
1668     {
1669       case 1:
1670       {
1671         // Assuming 8-bpp
1672         *((Uint8 *)surface->pixels + ypitch + x) = color;
1673       }
1674       break;
1675
1676       case 2:
1677       {
1678         // Probably 15-bpp or 16-bpp
1679         *((Uint16 *)surface->pixels + ypitch + x) = color;
1680       }
1681       break;
1682
1683       case 3:
1684       {
1685         // Slow 24-bpp mode, usually not used
1686         Uint8 *pix;
1687         int shift;
1688
1689         // Gack - slow, but endian correct
1690         pix = (Uint8 *)surface->pixels + ypitch + x * 3;
1691         shift = surface->format->Rshift;
1692         *(pix + shift / 8) = color>>shift;
1693         shift = surface->format->Gshift;
1694         *(pix + shift / 8) = color>>shift;
1695         shift = surface->format->Bshift;
1696         *(pix + shift / 8) = color>>shift;
1697       }
1698       break;
1699
1700       case 4:
1701       {
1702         // Probably 32-bpp
1703         *((Uint32 *)surface->pixels + ypitch + x) = color;
1704       }
1705       break;
1706     }
1707   }
1708 }
1709
1710 static void sge_HLine(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
1711                       Uint32 Color)
1712 {
1713   SDL_Rect l;
1714
1715   if (SDL_MUSTLOCK(Surface))
1716   {
1717     if (SDL_LockSurface(Surface) < 0)
1718     {
1719       return;
1720     }
1721   }
1722
1723   if (x1 > x2)
1724   {
1725     Sint16 tmp = x1;
1726     x1 = x2;
1727     x2 = tmp;
1728   }
1729
1730   // Do the clipping
1731   if (y < 0 || y > Surface->h - 1 || x1 > Surface->w - 1 || x2 < 0)
1732     return;
1733   if (x1 < 0)
1734     x1 = 0;
1735   if (x2 > Surface->w - 1)
1736     x2 = Surface->w - 1;
1737
1738   l.x = x1;
1739   l.y = y;
1740   l.w = x2 - x1 + 1;
1741   l.h = 1;
1742
1743   SDL_FillRect(Surface, &l, Color);
1744
1745   if (SDL_MUSTLOCK(Surface))
1746   {
1747     SDL_UnlockSurface(Surface);
1748   }
1749 }
1750
1751 static void sge_HLineRGB(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
1752                          Uint8 R, Uint8 G, Uint8 B)
1753 {
1754   sge_HLine(Surface, x1, x2, y, SDL_MapRGB(Surface->format, R, G, B));
1755 }
1756
1757 static void _HLine(SDL_Surface *Surface, Sint16 x1, Sint16 x2, Sint16 y,
1758                    Uint32 Color)
1759 {
1760   SDL_Rect l;
1761
1762   if (x1 > x2)
1763   {
1764     Sint16 tmp = x1;
1765     x1 = x2;
1766     x2 = tmp;
1767   }
1768
1769   // Do the clipping
1770   if (y < 0 || y > Surface->h - 1 || x1 > Surface->w - 1 || x2 < 0)
1771     return;
1772   if (x1 < 0)
1773     x1 = 0;
1774   if (x2 > Surface->w - 1)
1775     x2 = Surface->w - 1;
1776
1777   l.x = x1;
1778   l.y = y;
1779   l.w = x2 - x1 + 1;
1780   l.h = 1;
1781
1782   SDL_FillRect(Surface, &l, Color);
1783 }
1784
1785 static void sge_VLine(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
1786                       Uint32 Color)
1787 {
1788   SDL_Rect l;
1789
1790   if (SDL_MUSTLOCK(Surface))
1791   {
1792     if (SDL_LockSurface(Surface) < 0)
1793     {
1794       return;
1795     }
1796   }
1797
1798   if (y1 > y2)
1799   {
1800     Sint16 tmp = y1;
1801     y1 = y2;
1802     y2 = tmp;
1803   }
1804
1805   // Do the clipping
1806   if (x < 0 || x > Surface->w - 1 || y1 > Surface->h - 1 || y2 < 0)
1807     return;
1808   if (y1 < 0)
1809     y1 = 0;
1810   if (y2 > Surface->h - 1)
1811     y2 = Surface->h - 1;
1812
1813   l.x = x;
1814   l.y = y1;
1815   l.w = 1;
1816   l.h = y2 - y1 + 1;
1817
1818   SDL_FillRect(Surface, &l, Color);
1819
1820   if (SDL_MUSTLOCK(Surface))
1821   {
1822     SDL_UnlockSurface(Surface);
1823   }
1824 }
1825
1826 static void sge_VLineRGB(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
1827                          Uint8 R, Uint8 G, Uint8 B)
1828 {
1829   sge_VLine(Surface, x, y1, y2, SDL_MapRGB(Surface->format, R, G, B));
1830 }
1831
1832 static void _VLine(SDL_Surface *Surface, Sint16 x, Sint16 y1, Sint16 y2,
1833                    Uint32 Color)
1834 {
1835   SDL_Rect l;
1836
1837   if (y1 > y2)
1838   {
1839     Sint16 tmp = y1;
1840     y1 = y2;
1841     y2 = tmp;
1842   }
1843
1844   // Do the clipping
1845   if (x < 0 || x > Surface->w - 1 || y1 > Surface->h - 1 || y2 < 0)
1846     return;
1847   if (y1 < 0)
1848     y1 = 0;
1849   if (y2 > Surface->h - 1)
1850     y2 = Surface->h - 1;
1851
1852   l.x = x;
1853   l.y = y1;
1854   l.w = 1;
1855   l.h = y2 - y1 + 1;
1856
1857   SDL_FillRect(Surface, &l, Color);
1858 }
1859 #endif
1860
1861 static void sge_DoLine(SDL_Surface *Surface, Sint16 x1, Sint16 y1,
1862                        Sint16 x2, Sint16 y2, Uint32 Color,
1863                        void Callback(SDL_Surface *Surf, Sint16 X, Sint16 Y,
1864                                      Uint32 Color))
1865 {
1866   Sint16 dx, dy, sdx, sdy, x, y, px, py;
1867
1868   dx = x2 - x1;
1869   dy = y2 - y1;
1870
1871   sdx = (dx < 0) ? -1 : 1;
1872   sdy = (dy < 0) ? -1 : 1;
1873
1874   dx = sdx * dx + 1;
1875   dy = sdy * dy + 1;
1876
1877   x = y = 0;
1878
1879   px = x1;
1880   py = y1;
1881
1882   if (dx >= dy)
1883   {
1884     for (x = 0; x < dx; x++)
1885     {
1886       Callback(Surface, px, py, Color);
1887
1888       y += dy;
1889       if (y >= dx)
1890       {
1891         y -= dx;
1892         py += sdy;
1893       }
1894
1895       px += sdx;
1896     }
1897   }
1898   else
1899   {
1900     for (y = 0; y < dy; y++)
1901     {
1902       Callback(Surface, px, py, Color);
1903
1904       x += dx;
1905       if (x >= dy)
1906       {
1907         x -= dy;
1908         px += sdx;
1909       }
1910
1911       py += sdy;
1912     }
1913   }
1914 }
1915
1916 #if 0
1917 static void sge_DoLineRGB(SDL_Surface *Surface, Sint16 X1, Sint16 Y1,
1918                           Sint16 X2, Sint16 Y2, Uint8 R, Uint8 G, Uint8 B,
1919                           void Callback(SDL_Surface *Surf, Sint16 X, Sint16 Y,
1920                                         Uint32 Color))
1921 {
1922   sge_DoLine(Surface, X1, Y1, X2, Y2,
1923              SDL_MapRGB(Surface->format, R, G, B), Callback);
1924 }
1925 #endif
1926
1927 void sge_Line(SDL_Surface *Surface, Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2,
1928               Uint32 Color)
1929 {
1930   if (SDL_MUSTLOCK(Surface))
1931   {
1932     if (SDL_LockSurface(Surface) < 0)
1933       return;
1934    }
1935
1936    // Draw the line
1937    sge_DoLine(Surface, x1, y1, x2, y2, Color, _PutPixel);
1938
1939    // unlock the display
1940    if (SDL_MUSTLOCK(Surface))
1941    {
1942       SDL_UnlockSurface(Surface);
1943    }
1944 }
1945
1946 #if 0
1947 static void sge_LineRGB(SDL_Surface *Surface, Sint16 x1, Sint16 y1, Sint16 x2,
1948                         Sint16 y2, Uint8 R, Uint8 G, Uint8 B)
1949 {
1950   sge_Line(Surface, x1, y1, x2, y2, SDL_MapRGB(Surface->format, R, G, B));
1951 }
1952 #endif
1953
1954 void SDLPutPixel(Bitmap *dst_bitmap, int x, int y, Pixel pixel)
1955 {
1956   sge_PutPixel(dst_bitmap->surface, x, y, pixel);
1957 }
1958
1959
1960 // ----------------------------------------------------------------------------
1961 // quick (no, it's slow) and dirty hack to "invert" rectangle inside SDL surface
1962 // ----------------------------------------------------------------------------
1963
1964 void SDLCopyInverseMasked(Bitmap *src_bitmap, Bitmap *dst_bitmap,
1965                           int src_x, int src_y, int width, int height,
1966                           int dst_x, int dst_y)
1967 {
1968   int x, y;
1969
1970   for (y = 0; y < height; y++)
1971   {
1972     for (x = 0; x < width; x++)
1973     {
1974       Uint32 pixel = SDLGetPixel(src_bitmap, src_x + x, src_y + y);
1975
1976       if (pixel != BLACK_PIXEL)
1977         SDLPutPixel(dst_bitmap, dst_x + x, dst_y + y, BLACK_PIXEL);
1978     }
1979   }
1980 }
1981
1982
1983 // ============================================================================
1984 // The following functions were taken from the SDL_gfx library version 2.0.3
1985 // (Rotozoomer) by Andreas Schiffler
1986 // http://www.ferzkopp.net/Software/SDL_gfx-2.0/index.html
1987 // ============================================================================
1988
1989 // ----------------------------------------------------------------------------
1990 // 32 bit zoomer
1991 //
1992 // zoomes 32bit RGBA/ABGR 'src' surface to 'dst' surface.
1993 // ----------------------------------------------------------------------------
1994
1995 typedef struct
1996 {
1997   Uint8 r;
1998   Uint8 g;
1999   Uint8 b;
2000   Uint8 a;
2001 } tColorRGBA;
2002
2003 static int zoomSurfaceRGBA_scaleDownBy2(SDL_Surface *src, SDL_Surface *dst)
2004 {
2005   int x, y;
2006   tColorRGBA *sp, *csp, *dp;
2007   int dgap;
2008
2009   // pointer setup
2010   sp = csp = (tColorRGBA *) src->pixels;
2011   dp = (tColorRGBA *) dst->pixels;
2012   dgap = dst->pitch - dst->w * 4;
2013
2014   for (y = 0; y < dst->h; y++)
2015   {
2016     sp = csp;
2017
2018     for (x = 0; x < dst->w; x++)
2019     {
2020       tColorRGBA *sp0 = sp;
2021       tColorRGBA *sp1 = (tColorRGBA *) ((Uint8 *) sp + src->pitch);
2022       tColorRGBA *sp00 = &sp0[0];
2023       tColorRGBA *sp01 = &sp0[1];
2024       tColorRGBA *sp10 = &sp1[0];
2025       tColorRGBA *sp11 = &sp1[1];
2026       tColorRGBA new;
2027
2028       // create new color pixel from all four source color pixels
2029       new.r = (sp00->r + sp01->r + sp10->r + sp11->r) / 4;
2030       new.g = (sp00->g + sp01->g + sp10->g + sp11->g) / 4;
2031       new.b = (sp00->b + sp01->b + sp10->b + sp11->b) / 4;
2032       new.a = (sp00->a + sp01->a + sp10->a + sp11->a) / 4;
2033
2034       // draw
2035       *dp = new;
2036
2037       // advance source pointers
2038       sp += 2;
2039
2040       // advance destination pointer
2041       dp++;
2042     }
2043
2044     // advance source pointer
2045     csp = (tColorRGBA *) ((Uint8 *) csp + 2 * src->pitch);
2046
2047     // advance destination pointers
2048     dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
2049   }
2050
2051   return 0;
2052 }
2053
2054 static int zoomSurfaceRGBA(SDL_Surface *src, SDL_Surface *dst)
2055 {
2056   int x, y, *sax, *say, *csax, *csay;
2057   float sx, sy;
2058   tColorRGBA *sp, *csp, *csp0, *dp;
2059   int dgap;
2060
2061   // use specialized zoom function when scaling down to exactly half size
2062   if (src->w == 2 * dst->w &&
2063       src->h == 2 * dst->h)
2064     return zoomSurfaceRGBA_scaleDownBy2(src, dst);
2065
2066   // variable setup
2067   sx = (float) src->w / (float) dst->w;
2068   sy = (float) src->h / (float) dst->h;
2069
2070   // allocate memory for row increments
2071   csax = sax = (int *)checked_malloc((dst->w + 1) * sizeof(Uint32));
2072   csay = say = (int *)checked_malloc((dst->h + 1) * sizeof(Uint32));
2073
2074   // precalculate row increments
2075   for (x = 0; x <= dst->w; x++)
2076     *csax++ = (int)(sx * x);
2077
2078   for (y = 0; y <= dst->h; y++)
2079     *csay++ = (int)(sy * y);
2080
2081   // pointer setup
2082   sp = csp = csp0 = (tColorRGBA *) src->pixels;
2083   dp = (tColorRGBA *) dst->pixels;
2084   dgap = dst->pitch - dst->w * 4;
2085
2086   csay = say;
2087   for (y = 0; y < dst->h; y++)
2088   {
2089     sp = csp;
2090     csax = sax;
2091
2092     for (x = 0; x < dst->w; x++)
2093     {
2094       // draw
2095       *dp = *sp;
2096
2097       // advance source pointers
2098       csax++;
2099       sp = csp + *csax;
2100
2101       // advance destination pointer
2102       dp++;
2103     }
2104
2105     // advance source pointer
2106     csay++;
2107     csp = (tColorRGBA *) ((Uint8 *) csp0 + *csay * src->pitch);
2108
2109     // advance destination pointers
2110     dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
2111   }
2112
2113   free(sax);
2114   free(say);
2115
2116   return 0;
2117 }
2118
2119 // ----------------------------------------------------------------------------
2120 // 8 bit zoomer
2121 //
2122 // zoomes 8 bit palette/Y 'src' surface to 'dst' surface
2123 // ----------------------------------------------------------------------------
2124
2125 static int zoomSurfaceY(SDL_Surface * src, SDL_Surface * dst)
2126 {
2127   Uint32 x, y, sx, sy, *sax, *say, *csax, *csay, csx, csy;
2128   Uint8 *sp, *dp, *csp;
2129   int dgap;
2130
2131   // variable setup
2132   sx = (Uint32) (65536.0 * (float) src->w / (float) dst->w);
2133   sy = (Uint32) (65536.0 * (float) src->h / (float) dst->h);
2134
2135   // allocate memory for row increments
2136   sax = (Uint32 *)checked_malloc(dst->w * sizeof(Uint32));
2137   say = (Uint32 *)checked_malloc(dst->h * sizeof(Uint32));
2138
2139   // precalculate row increments
2140   csx = 0;
2141   csax = sax;
2142   for (x = 0; x < dst->w; x++)
2143   {
2144     csx += sx;
2145     *csax = (csx >> 16);
2146     csx &= 0xffff;
2147     csax++;
2148   }
2149
2150   csy = 0;
2151   csay = say;
2152   for (y = 0; y < dst->h; y++)
2153   {
2154     csy += sy;
2155     *csay = (csy >> 16);
2156     csy &= 0xffff;
2157     csay++;
2158   }
2159
2160   csx = 0;
2161   csax = sax;
2162   for (x = 0; x < dst->w; x++)
2163   {
2164     csx += (*csax);
2165     csax++;
2166   }
2167
2168   csy = 0;
2169   csay = say;
2170   for (y = 0; y < dst->h; y++)
2171   {
2172     csy += (*csay);
2173     csay++;
2174   }
2175
2176   // pointer setup
2177   sp = csp = (Uint8 *) src->pixels;
2178   dp = (Uint8 *) dst->pixels;
2179   dgap = dst->pitch - dst->w;
2180
2181   // draw
2182   csay = say;
2183   for (y = 0; y < dst->h; y++)
2184   {
2185     csax = sax;
2186     sp = csp;
2187     for (x = 0; x < dst->w; x++)
2188     {
2189       // draw
2190       *dp = *sp;
2191
2192       // advance source pointers
2193       sp += (*csax);
2194       csax++;
2195
2196       // advance destination pointer
2197       dp++;
2198     }
2199
2200     // advance source pointer (for row)
2201     csp += ((*csay) * src->pitch);
2202     csay++;
2203
2204     // advance destination pointers
2205     dp += dgap;
2206   }
2207
2208   free(sax);
2209   free(say);
2210
2211   return 0;
2212 }
2213
2214 // ----------------------------------------------------------------------------
2215 // zoomSurface()
2216 //
2217 // Zooms a 32bit or 8bit 'src' surface to newly created 'dst' surface.
2218 // 'zoomx' and 'zoomy' are scaling factors for width and height.
2219 // If the surface is not 8bit or 32bit RGBA/ABGR it will be converted
2220 // into a 32bit RGBA format on the fly.
2221 // ----------------------------------------------------------------------------
2222
2223 static SDL_Surface *zoomSurface(SDL_Surface *src, int dst_width, int dst_height)
2224 {
2225   SDL_Surface *zoom_src = NULL;
2226   SDL_Surface *zoom_dst = NULL;
2227   boolean is_converted = FALSE;
2228   boolean is_32bit;
2229   int i;
2230
2231   if (src == NULL)
2232     return NULL;
2233
2234   // determine if source surface is 32 bit or 8 bit
2235   is_32bit = (src->format->BitsPerPixel == 32);
2236
2237   if (is_32bit || src->format->BitsPerPixel == 8)
2238   {
2239     // use source surface 'as is'
2240     zoom_src = src;
2241   }
2242   else
2243   {
2244     // new source surface is 32 bit with a defined RGB ordering
2245     zoom_src = SDL_CreateRGBSurface(SURFACE_FLAGS, src->w, src->h, 32,
2246                                     0x000000ff, 0x0000ff00, 0x00ff0000,
2247                                     (src->format->Amask ? 0xff000000 : 0));
2248     SDL_BlitSurface(src, NULL, zoom_src, NULL);
2249     is_32bit = TRUE;
2250     is_converted = TRUE;
2251   }
2252
2253   // allocate surface to completely contain the zoomed surface
2254   if (is_32bit)
2255   {
2256     // target surface is 32 bit with source RGBA/ABGR ordering
2257     zoom_dst = SDL_CreateRGBSurface(SURFACE_FLAGS, dst_width, dst_height, 32,
2258                                     zoom_src->format->Rmask,
2259                                     zoom_src->format->Gmask,
2260                                     zoom_src->format->Bmask,
2261                                     zoom_src->format->Amask);
2262   }
2263   else
2264   {
2265     // target surface is 8 bit
2266     zoom_dst = SDL_CreateRGBSurface(SURFACE_FLAGS, dst_width, dst_height, 8,
2267                                     0, 0, 0, 0);
2268   }
2269
2270   // lock source surface
2271   SDL_LockSurface(zoom_src);
2272
2273   // check which kind of surface we have
2274   if (is_32bit)
2275   {
2276     // call the 32 bit transformation routine to do the zooming
2277     zoomSurfaceRGBA(zoom_src, zoom_dst);
2278   }
2279   else
2280   {
2281     // copy palette
2282     for (i = 0; i < zoom_src->format->palette->ncolors; i++)
2283       zoom_dst->format->palette->colors[i] =
2284         zoom_src->format->palette->colors[i];
2285     zoom_dst->format->palette->ncolors = zoom_src->format->palette->ncolors;
2286
2287     // call the 8 bit transformation routine to do the zooming
2288     zoomSurfaceY(zoom_src, zoom_dst);
2289   }
2290
2291   // unlock source surface
2292   SDL_UnlockSurface(zoom_src);
2293
2294   // free temporary surface
2295   if (is_converted)
2296     SDL_FreeSurface(zoom_src);
2297
2298   // return destination surface
2299   return zoom_dst;
2300 }
2301
2302 static SDL_Surface *SDLGetOpaqueSurface(SDL_Surface *surface)
2303 {
2304   SDL_Surface *new_surface;
2305
2306   if (surface == NULL)
2307     return NULL;
2308
2309   if ((new_surface = SDLGetNativeSurface(surface)) == NULL)
2310     Fail("SDLGetNativeSurface() failed");
2311
2312   // remove alpha channel from native non-transparent surface, if defined
2313   SDLSetAlpha(new_surface, FALSE, 0);
2314
2315   // remove transparent color from native non-transparent surface, if defined
2316   SDL_SetColorKey(new_surface, UNSET_TRANSPARENT_PIXEL, 0);
2317
2318   return new_surface;
2319 }
2320
2321 Bitmap *SDLZoomBitmap(Bitmap *src_bitmap, int dst_width, int dst_height)
2322 {
2323   Bitmap *dst_bitmap = CreateBitmapStruct();
2324   SDL_Surface *src_surface = src_bitmap->surface_masked;
2325   SDL_Surface *dst_surface;
2326
2327   dst_width  = MAX(1, dst_width);       // prevent zero bitmap width
2328   dst_height = MAX(1, dst_height);      // prevent zero bitmap height
2329
2330   dst_bitmap->width  = dst_width;
2331   dst_bitmap->height = dst_height;
2332
2333   // create zoomed temporary surface from source surface
2334   dst_surface = zoomSurface(src_surface, dst_width, dst_height);
2335
2336   // create native format destination surface from zoomed temporary surface
2337   SDLSetNativeSurface(&dst_surface);
2338
2339   // set color key for zoomed surface from source surface, if defined
2340   if (SDLHasColorKey(src_surface))
2341     SDLCopyColorKey(src_surface, dst_surface);
2342
2343   // create native non-transparent surface for opaque blitting
2344   dst_bitmap->surface = SDLGetOpaqueSurface(dst_surface);
2345
2346   // set native transparent surface for masked blitting
2347   dst_bitmap->surface_masked = dst_surface;
2348
2349   return dst_bitmap;
2350 }
2351
2352
2353 // ============================================================================
2354 // load image to bitmap
2355 // ============================================================================
2356
2357 Bitmap *SDLLoadImage(char *filename)
2358 {
2359   Bitmap *new_bitmap = CreateBitmapStruct();
2360   SDL_Surface *sdl_image_tmp;
2361
2362   if (program.headless)
2363   {
2364     // prevent sanity check warnings at later stage
2365     new_bitmap->width = new_bitmap->height = 1;
2366
2367     return new_bitmap;
2368   }
2369
2370   print_timestamp_init("SDLLoadImage");
2371
2372   print_timestamp_time(getBaseNamePtr(filename));
2373
2374   // load image to temporary surface
2375   if ((sdl_image_tmp = IMG_Load(filename)) == NULL)
2376     Fail("IMG_Load('%s') failed: %s", getBaseNamePtr(filename), SDL_GetError());
2377
2378   print_timestamp_time("IMG_Load");
2379
2380   UPDATE_BUSY_STATE();
2381
2382   // create native non-transparent surface for current image
2383   if ((new_bitmap->surface = SDLGetOpaqueSurface(sdl_image_tmp)) == NULL)
2384     Fail("SDLGetOpaqueSurface() failed");
2385
2386   print_timestamp_time("SDLGetNativeSurface (opaque)");
2387
2388   UPDATE_BUSY_STATE();
2389
2390   // set black pixel to transparent if no alpha channel / transparent color
2391   if (!SDLHasAlpha(sdl_image_tmp) &&
2392       !SDLHasColorKey(sdl_image_tmp))
2393     SDL_SetColorKey(sdl_image_tmp, SET_TRANSPARENT_PIXEL,
2394                     SDL_MapRGB(sdl_image_tmp->format, 0x00, 0x00, 0x00));
2395
2396   // create native transparent surface for current image
2397   if ((new_bitmap->surface_masked = SDLGetNativeSurface(sdl_image_tmp)) == NULL)
2398     Fail("SDLGetNativeSurface() failed");
2399
2400   print_timestamp_time("SDLGetNativeSurface (masked)");
2401
2402   UPDATE_BUSY_STATE();
2403
2404   // free temporary surface
2405   SDL_FreeSurface(sdl_image_tmp);
2406
2407   new_bitmap->width = new_bitmap->surface->w;
2408   new_bitmap->height = new_bitmap->surface->h;
2409
2410   print_timestamp_done("SDLLoadImage");
2411
2412   return new_bitmap;
2413 }
2414
2415
2416 // ----------------------------------------------------------------------------
2417 // custom cursor fuctions
2418 // ----------------------------------------------------------------------------
2419
2420 static SDL_Cursor *create_cursor(struct MouseCursorInfo *cursor_info)
2421 {
2422   return SDL_CreateCursor(cursor_info->data, cursor_info->mask,
2423                           cursor_info->width, cursor_info->height,
2424                           cursor_info->hot_x, cursor_info->hot_y);
2425 }
2426
2427 void SDLSetMouseCursor(struct MouseCursorInfo *cursor_info)
2428 {
2429   static struct MouseCursorInfo *last_cursor_info = NULL;
2430   static struct MouseCursorInfo *last_cursor_info2 = NULL;
2431   static SDL_Cursor *cursor_default = NULL;
2432   static SDL_Cursor *cursor_current = NULL;
2433
2434   // if invoked for the first time, store the SDL default cursor
2435   if (cursor_default == NULL)
2436     cursor_default = SDL_GetCursor();
2437
2438   // only create new cursor if cursor info (custom only) has changed
2439   if (cursor_info != NULL && cursor_info != last_cursor_info)
2440   {
2441     cursor_current = create_cursor(cursor_info);
2442     last_cursor_info = cursor_info;
2443   }
2444
2445   // only set new cursor if cursor info (custom or NULL) has changed
2446   if (cursor_info != last_cursor_info2)
2447     SDL_SetCursor(cursor_info ? cursor_current : cursor_default);
2448
2449   last_cursor_info2 = cursor_info;
2450 }
2451
2452
2453 // ============================================================================
2454 // audio functions
2455 // ============================================================================
2456
2457 void SDLOpenAudio(void)
2458 {
2459   if (program.headless)
2460     return;
2461
2462   if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
2463   {
2464     Warn("SDL_InitSubSystem() failed: %s", SDL_GetError());
2465
2466     return;
2467   }
2468
2469   if (Mix_OpenAudio(DEFAULT_AUDIO_SAMPLE_RATE, MIX_DEFAULT_FORMAT,
2470                     AUDIO_NUM_CHANNELS_STEREO,
2471                     setup.system.audio_fragment_size) < 0)
2472   {
2473     Warn("Mix_OpenAudio() failed: %s", SDL_GetError());
2474
2475     return;
2476   }
2477
2478   audio.sound_available = TRUE;
2479   audio.music_available = TRUE;
2480   audio.loops_available = TRUE;
2481   audio.sound_enabled = TRUE;
2482
2483   // set number of available mixer channels
2484   audio.num_channels = Mix_AllocateChannels(NUM_MIXER_CHANNELS);
2485   audio.music_channel = MUSIC_CHANNEL;
2486   audio.first_sound_channel = FIRST_SOUND_CHANNEL;
2487
2488   Mixer_InitChannels();
2489 }
2490
2491 void SDLCloseAudio(void)
2492 {
2493   Mix_HaltMusic();
2494   Mix_HaltChannel(-1);
2495
2496   Mix_CloseAudio();
2497   SDL_QuitSubSystem(SDL_INIT_AUDIO);
2498 }
2499
2500
2501 // ============================================================================
2502 // event functions
2503 // ============================================================================
2504
2505 void SDLWaitEvent(Event *event)
2506 {
2507   SDL_WaitEvent(event);
2508 }
2509
2510 void SDLCorrectRawMousePosition(int *x, int *y)
2511 {
2512   if (sdl_renderer == NULL)
2513     return;
2514
2515   // this corrects the raw mouse position for logical screen size within event
2516   // filters (correction done later by SDL library when handling mouse events)
2517
2518   SDL_Rect viewport;
2519   float scale_x, scale_y;
2520
2521   SDL_RenderGetViewport(sdl_renderer, &viewport);
2522   SDL_RenderGetScale(sdl_renderer, &scale_x, &scale_y);
2523
2524   *x = (int)(*x / scale_x);
2525   *y = (int)(*y / scale_y);
2526
2527   *x -= viewport.x;
2528   *y -= viewport.y;
2529 }
2530
2531
2532 // ============================================================================
2533 // joystick functions
2534 // ============================================================================
2535
2536 static void *sdl_joystick[MAX_PLAYERS];         // game controller or joystick
2537 static int sdl_js_axis_raw[MAX_PLAYERS][2];
2538 static int sdl_js_axis[MAX_PLAYERS][2];
2539 static int sdl_js_button[MAX_PLAYERS][2];
2540 static boolean sdl_is_controller[MAX_PLAYERS];
2541
2542 void SDLClearJoystickState(void)
2543 {
2544   int i, j;
2545
2546   for (i = 0; i < MAX_PLAYERS; i++)
2547   {
2548     for (j = 0; j < 2; j++)
2549     {
2550       sdl_js_axis_raw[i][j] = -1;
2551       sdl_js_axis[i][j] = 0;
2552       sdl_js_button[i][j] = 0;
2553     }
2554   }
2555 }
2556
2557 boolean SDLOpenJoystick(int nr)
2558 {
2559   if (nr < 0 || nr >= MAX_PLAYERS)
2560     return FALSE;
2561
2562   sdl_is_controller[nr] = SDL_IsGameController(nr);
2563
2564 #if DEBUG_JOYSTICKS
2565   Debug("joystick", "opening joystick %d (%s)",
2566         nr, (sdl_is_controller[nr] ? "game controller" : "joystick"));
2567 #endif
2568
2569   if (sdl_is_controller[nr])
2570     sdl_joystick[nr] = SDL_GameControllerOpen(nr);
2571   else
2572     sdl_joystick[nr] = SDL_JoystickOpen(nr);
2573
2574   return (sdl_joystick[nr] != NULL);
2575 }
2576
2577 void SDLCloseJoystick(int nr)
2578 {
2579   if (nr < 0 || nr >= MAX_PLAYERS)
2580     return;
2581
2582 #if DEBUG_JOYSTICKS
2583   Debug("joystick", "closing joystick %d", nr);
2584 #endif
2585
2586   if (sdl_is_controller[nr])
2587     SDL_GameControllerClose(sdl_joystick[nr]);
2588   else
2589     SDL_JoystickClose(sdl_joystick[nr]);
2590
2591   sdl_joystick[nr] = NULL;
2592 }
2593
2594 boolean SDLCheckJoystickOpened(int nr)
2595 {
2596   if (nr < 0 || nr >= MAX_PLAYERS)
2597     return FALSE;
2598
2599   return (sdl_joystick[nr] != NULL ? TRUE : FALSE);
2600 }
2601
2602 static void setJoystickAxis(int nr, int axis_id_raw, int axis_value)
2603 {
2604   int axis_id = (axis_id_raw == SDL_CONTROLLER_AXIS_LEFTX ||
2605                  axis_id_raw == SDL_CONTROLLER_AXIS_RIGHTX ? 0 :
2606                  axis_id_raw == SDL_CONTROLLER_AXIS_LEFTY ||
2607                  axis_id_raw == SDL_CONTROLLER_AXIS_RIGHTY ? 1 : -1);
2608
2609   if (nr < 0 || nr >= MAX_PLAYERS)
2610     return;
2611
2612   if (axis_id == -1)
2613     return;
2614
2615   // prevent (slightly jittering, but centered) axis A from resetting axis B
2616   if (ABS(axis_value) < JOYSTICK_PERCENT * JOYSTICK_MAX_AXIS_POS / 100 &&
2617       axis_id_raw != sdl_js_axis_raw[nr][axis_id])
2618     return;
2619
2620   sdl_js_axis[nr][axis_id] = axis_value;
2621   sdl_js_axis_raw[nr][axis_id] = axis_id_raw;
2622 }
2623
2624 static void setJoystickButton(int nr, int button_id_raw, int button_state)
2625 {
2626   int button_id = (button_id_raw == SDL_CONTROLLER_BUTTON_A ||
2627                    button_id_raw == SDL_CONTROLLER_BUTTON_X ||
2628                    button_id_raw == SDL_CONTROLLER_BUTTON_LEFTSHOULDER ||
2629                    button_id_raw == SDL_CONTROLLER_BUTTON_LEFTSTICK ||
2630                    button_id_raw == SDL_CONTROLLER_BUTTON_RIGHTSTICK ? 0 :
2631                    button_id_raw == SDL_CONTROLLER_BUTTON_B ||
2632                    button_id_raw == SDL_CONTROLLER_BUTTON_Y ||
2633                    button_id_raw == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER ? 1 :
2634                    -1);
2635
2636   if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_LEFT)
2637     sdl_js_axis[nr][0] = button_state * JOYSTICK_XLEFT;
2638   else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
2639     sdl_js_axis[nr][0] = button_state * JOYSTICK_XRIGHT;
2640   else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_UP)
2641     sdl_js_axis[nr][1] = button_state * JOYSTICK_YUPPER;
2642   else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
2643     sdl_js_axis[nr][1] = button_state * JOYSTICK_YLOWER;
2644
2645   if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_LEFT ||
2646       button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_RIGHT ||
2647       button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_UP ||
2648       button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
2649     sdl_js_axis_raw[nr][0] = sdl_js_axis_raw[nr][1] = -1;
2650
2651   if (nr < 0 || nr >= MAX_PLAYERS)
2652     return;
2653
2654   if (button_id == -1)
2655     return;
2656
2657   sdl_js_button[nr][button_id] = button_state;
2658 }
2659
2660 void HandleJoystickEvent(Event *event)
2661 {
2662   // when using joystick, disable overlay touch buttons
2663   runtime.uses_touch_device = FALSE;
2664
2665   switch (event->type)
2666   {
2667     case SDL_CONTROLLERDEVICEADDED:
2668 #if DEBUG_JOYSTICKS
2669       Debug("joystick", "SDL_CONTROLLERDEVICEADDED: device %d added",
2670             event->cdevice.which);
2671 #endif
2672       InitJoysticks();
2673       break;
2674
2675     case SDL_CONTROLLERDEVICEREMOVED:
2676 #if DEBUG_JOYSTICKS
2677       Debug("joystick", "SDL_CONTROLLERDEVICEREMOVED: device %d removed",
2678             event->cdevice.which);
2679 #endif
2680       InitJoysticks();
2681       break;
2682
2683     case SDL_CONTROLLERAXISMOTION:
2684 #if DEBUG_JOYSTICKS
2685       Debug("joystick", "SDL_CONTROLLERAXISMOTION: device %d, axis %d: %d",
2686             event->caxis.which, event->caxis.axis, event->caxis.value);
2687 #endif
2688       setJoystickAxis(event->caxis.which,
2689                       event->caxis.axis,
2690                       event->caxis.value);
2691       break;
2692
2693     case SDL_CONTROLLERBUTTONDOWN:
2694 #if DEBUG_JOYSTICKS
2695       Debug("joystick", "SDL_CONTROLLERBUTTONDOWN: device %d, button %d",
2696             event->cbutton.which, event->cbutton.button);
2697 #endif
2698       setJoystickButton(event->cbutton.which,
2699                         event->cbutton.button,
2700                         TRUE);
2701       break;
2702
2703     case SDL_CONTROLLERBUTTONUP:
2704 #if DEBUG_JOYSTICKS
2705       Debug("joystick", "SDL_CONTROLLERBUTTONUP: device %d, button %d",
2706             event->cbutton.which, event->cbutton.button);
2707 #endif
2708       setJoystickButton(event->cbutton.which,
2709                         event->cbutton.button,
2710                         FALSE);
2711       break;
2712
2713     case SDL_JOYAXISMOTION:
2714       if (sdl_is_controller[event->jaxis.which])
2715         break;
2716
2717 #if DEBUG_JOYSTICKS
2718       Debug("joystick", "SDL_JOYAXISMOTION: device %d, axis %d: %d",
2719             event->jaxis.which, event->jaxis.axis, event->jaxis.value);
2720 #endif
2721       if (event->jaxis.axis < 4)
2722         setJoystickAxis(event->jaxis.which,
2723                         event->jaxis.axis,
2724                         event->jaxis.value);
2725       break;
2726
2727     case SDL_JOYBUTTONDOWN:
2728       if (sdl_is_controller[event->jaxis.which])
2729         break;
2730
2731 #if DEBUG_JOYSTICKS
2732       Debug("joystick", "SDL_JOYBUTTONDOWN: device %d, button %d",
2733             event->jbutton.which, event->jbutton.button);
2734 #endif
2735       if (event->jbutton.button < 4)
2736         setJoystickButton(event->jbutton.which,
2737                           event->jbutton.button,
2738                           TRUE);
2739       break;
2740
2741     case SDL_JOYBUTTONUP:
2742       if (sdl_is_controller[event->jaxis.which])
2743         break;
2744
2745 #if DEBUG_JOYSTICKS
2746       Debug("joystick", "SDL_JOYBUTTONUP: device %d, button %d",
2747             event->jbutton.which, event->jbutton.button);
2748 #endif
2749       if (event->jbutton.button < 4)
2750         setJoystickButton(event->jbutton.which,
2751                           event->jbutton.button,
2752                           FALSE);
2753       break;
2754
2755     default:
2756       break;
2757   }
2758 }
2759
2760 void SDLInitJoysticks(void)
2761 {
2762   static boolean sdl_joystick_subsystem_initialized = FALSE;
2763   boolean print_warning = !sdl_joystick_subsystem_initialized;
2764   char *mappings_file_base = getPath2(options.conf_directory,
2765                                       GAMECONTROLLER_BASENAME);
2766   char *mappings_file_user = getPath2(getMainUserGameDataDir(),
2767                                       GAMECONTROLLER_BASENAME);
2768   int num_mappings;
2769   int i;
2770
2771   if (!sdl_joystick_subsystem_initialized)
2772   {
2773     sdl_joystick_subsystem_initialized = TRUE;
2774
2775     SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
2776
2777     if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0)
2778       Fail("SDL_Init() failed: %s", SDL_GetError());
2779
2780     num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_base);
2781
2782     // the included game controller base mappings should always be found
2783     if (num_mappings == -1)
2784       Warn("no game controller base mappings found");
2785 #if DEBUG_JOYSTICKS
2786     else
2787       Debug("joystick", "%d game controller base mapping(s) added",
2788             num_mappings);
2789 #endif
2790
2791     num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_user);
2792
2793 #if DEBUG_JOYSTICKS
2794     // the personal game controller user mappings may or may not be found
2795     if (num_mappings == -1)
2796       Warn("no game controller user mappings found");
2797     else
2798       Debug("joystick", , "%d game controller user mapping(s) added",
2799             num_mappings);
2800
2801     Debug("joystick", "%d joystick(s) found:", SDL_NumJoysticks());
2802 #endif
2803
2804     checked_free(mappings_file_base);
2805     checked_free(mappings_file_user);
2806
2807 #if DEBUG_JOYSTICKS
2808     for (i = 0; i < SDL_NumJoysticks(); i++)
2809     {
2810       const char *name, *type;
2811
2812       if (SDL_IsGameController(i))
2813       {
2814         name = SDL_GameControllerNameForIndex(i);
2815         type = "game controller";
2816       }
2817       else
2818       {
2819         name = SDL_JoystickNameForIndex(i);
2820         type = "joystick";
2821       }
2822
2823       Debug("joystick", "- joystick %d (%s): '%s'",
2824             i, type, (name ? name : "(Unknown)"));
2825     }
2826 #endif
2827   }
2828
2829   // assign joysticks from configured to connected joystick for all players
2830   for (i = 0; i < MAX_PLAYERS; i++)
2831   {
2832     // get configured joystick for this player
2833     char *device_name = setup.input[i].joy.device_name;
2834     int joystick_nr = getJoystickNrFromDeviceName(device_name);
2835
2836     if (joystick_nr >= SDL_NumJoysticks())
2837     {
2838       if (setup.input[i].use_joystick && print_warning)
2839         Warn("cannot find joystick %d", joystick_nr);
2840
2841       joystick_nr = -1;
2842     }
2843
2844     // store configured joystick number for each player
2845     joystick.nr[i] = joystick_nr;
2846   }
2847
2848   // now open all connected joysticks (regardless if configured or not)
2849   for (i = 0; i < SDL_NumJoysticks(); i++)
2850   {
2851     // this allows subsequent calls to 'InitJoysticks' for re-initialization
2852     if (SDLCheckJoystickOpened(i))
2853       SDLCloseJoystick(i);
2854
2855     if (SDLOpenJoystick(i))
2856       joystick.status = JOYSTICK_ACTIVATED;
2857     else if (print_warning)
2858       Warn("cannot open joystick %d", i);
2859   }
2860
2861   SDLClearJoystickState();
2862 }
2863
2864 boolean SDLReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
2865 {
2866   if (nr < 0 || nr >= MAX_PLAYERS)
2867     return FALSE;
2868
2869   if (x != NULL)
2870     *x = sdl_js_axis[nr][0];
2871   if (y != NULL)
2872     *y = sdl_js_axis[nr][1];
2873
2874   if (b1 != NULL)
2875     *b1 = sdl_js_button[nr][0];
2876   if (b2 != NULL)
2877     *b2 = sdl_js_button[nr][1];
2878
2879   return TRUE;
2880 }
2881
2882
2883 // ============================================================================
2884 // touch input overlay functions
2885 // ============================================================================
2886
2887 #if defined(USE_TOUCH_INPUT_OVERLAY)
2888 static void DrawTouchInputOverlay_ShowGrid(int alpha)
2889 {
2890   SDL_Rect rect;
2891   int grid_xsize = overlay.grid_xsize;
2892   int grid_ysize = overlay.grid_ysize;
2893   int x, y;
2894
2895   SDL_SetRenderDrawColor(sdl_renderer, 255, 255, 255, alpha);
2896   SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
2897
2898   for (x = 0; x < grid_xsize; x++)
2899   {
2900     rect.x = (x + 0) * video.screen_width / grid_xsize;
2901     rect.w = (x + 1) * video.screen_width / grid_xsize - rect.x;
2902
2903     for (y = 0; y < grid_ysize; y++)
2904     {
2905       rect.y = (y + 0) * video.screen_height / grid_ysize;
2906       rect.h = (y + 1) * video.screen_height / grid_ysize - rect.y;
2907
2908       if (overlay.grid_button[x][y] == CHAR_GRID_BUTTON_NONE)
2909         SDL_RenderDrawRect(sdl_renderer, &rect);
2910     }
2911   }
2912
2913   SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
2914 }
2915
2916 static void RenderFillRectangle(int x, int y, int width, int height)
2917 {
2918   SDL_Rect rect = { x, y, width, height };
2919
2920   SDL_RenderFillRect(sdl_renderer, &rect);
2921 }
2922
2923 static void DrawTouchInputOverlay_ShowGridButtons(int alpha)
2924 {
2925   static int alpha_direction = 0;
2926   static int alpha_highlight = 0;
2927   int alpha_max = ALPHA_FROM_TRANSPARENCY(setup.touch.transparency);
2928   int alpha_step = ALPHA_FADING_STEPSIZE(alpha_max);
2929   SDL_Rect rect;
2930   int grid_xsize = overlay.grid_xsize;
2931   int grid_ysize = overlay.grid_ysize;
2932   int x, y;
2933
2934   if (alpha == alpha_max)
2935   {
2936     if (alpha_direction < 0)
2937     {
2938       alpha_highlight = MAX(0, alpha_highlight - alpha_step);
2939
2940       if (alpha_highlight == 0)
2941         alpha_direction = 1;
2942     }
2943     else
2944     {
2945       alpha_highlight = MIN(alpha_highlight + alpha_step, alpha_max);
2946
2947       if (alpha_highlight == alpha_max)
2948         alpha_direction = -1;
2949     }
2950   }
2951   else
2952   {
2953     alpha_direction = 1;
2954     alpha_highlight = alpha;
2955   }
2956
2957   SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
2958
2959   for (x = 0; x < grid_xsize; x++)
2960   {
2961     for (y = 0; y < grid_ysize; y++)
2962     {
2963       int grid_button = overlay.grid_button[x][y];
2964       int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
2965       int alpha_draw = alpha;
2966       int outline_border = MV_NONE;
2967       int border_size = 2;
2968       boolean draw_outlined = setup.touch.draw_outlined;
2969       boolean draw_pressed = setup.touch.draw_pressed;
2970
2971       if (grid_button == CHAR_GRID_BUTTON_NONE)
2972         continue;
2973
2974       if (grid_button == overlay.grid_button_highlight)
2975       {
2976         draw_outlined = FALSE;
2977         alpha_draw = MIN((float)alpha_highlight * 1.5, SDL_ALPHA_OPAQUE);
2978       }
2979
2980       if (draw_pressed && overlay.grid_button_action & grid_button_action)
2981       {
2982         if (draw_outlined)
2983           draw_outlined = FALSE;
2984         else
2985           alpha_draw = MIN((float)alpha_draw * 1.5, SDL_ALPHA_OPAQUE);
2986       }
2987
2988       SDL_SetRenderDrawColor(sdl_renderer, 255, 255, 255, alpha_draw);
2989
2990       rect.x = (x + 0) * video.screen_width  / grid_xsize;
2991       rect.y = (y + 0) * video.screen_height / grid_ysize;
2992       rect.w = (x + 1) * video.screen_width  / grid_xsize - rect.x;
2993       rect.h = (y + 1) * video.screen_height / grid_ysize - rect.y;
2994
2995       if (x == 0 || overlay.grid_button[x - 1][y] != grid_button)
2996       {
2997         rect.x += border_size;
2998         rect.w -= border_size;
2999
3000         outline_border |= MV_LEFT;
3001       }
3002
3003       if (x == grid_xsize - 1 || overlay.grid_button[x + 1][y] != grid_button)
3004       {
3005         rect.w -= border_size;
3006
3007         outline_border |= MV_RIGHT;
3008       }
3009
3010       if (y == 0 || overlay.grid_button[x][y - 1] != grid_button)
3011       {
3012         rect.y += border_size;
3013         rect.h -= border_size;
3014
3015         outline_border |= MV_UP;
3016       }
3017
3018       if (y == grid_ysize - 1 || overlay.grid_button[x][y + 1] != grid_button)
3019       {
3020         rect.h -= border_size;
3021
3022         outline_border |= MV_DOWN;
3023       }
3024
3025       if (draw_outlined)
3026       {
3027         int rect_x = rect.x +
3028           (outline_border & MV_LEFT  ? border_size : 0);
3029         int rect_w = rect.w -
3030           (outline_border & MV_LEFT  ? border_size : 0) -
3031           (outline_border & MV_RIGHT ? border_size : 0);
3032
3033         if (outline_border & MV_LEFT)
3034           RenderFillRectangle(rect.x, rect.y, border_size, rect.h);
3035
3036         if (outline_border & MV_RIGHT)
3037           RenderFillRectangle(rect.x + rect.w - border_size, rect.y,
3038                               border_size, rect.h);
3039
3040         if (outline_border & MV_UP)
3041           RenderFillRectangle(rect_x, rect.y, rect_w, border_size);
3042
3043         if (outline_border & MV_DOWN)
3044           RenderFillRectangle(rect_x, rect.y + rect.h - border_size,
3045                               rect_w, border_size);
3046       }
3047       else
3048       {
3049         SDL_RenderFillRect(sdl_renderer, &rect);
3050       }
3051     }
3052   }
3053
3054   SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
3055 }
3056
3057 static void DrawTouchInputOverlay(void)
3058 {
3059   static boolean deactivated = TRUE;
3060   static boolean show_grid = FALSE;
3061   static int alpha = 0;
3062   int alpha_max = ALPHA_FROM_TRANSPARENCY(setup.touch.transparency);
3063   int alpha_step = ALPHA_FADING_STEPSIZE(alpha_max);
3064   boolean active = (overlay.enabled && overlay.active);
3065
3066   if (!active && deactivated)
3067     return;
3068
3069   if (active)
3070   {
3071     if (alpha < alpha_max)
3072       alpha = MIN(alpha + alpha_step, alpha_max);
3073
3074     deactivated = FALSE;
3075   }
3076   else
3077   {
3078     alpha = MAX(0, alpha - alpha_step);
3079
3080     if (alpha == 0)
3081       deactivated = TRUE;
3082   }
3083
3084   if (overlay.show_grid)
3085     show_grid = TRUE;
3086   else if (deactivated)
3087     show_grid = FALSE;
3088
3089   if (show_grid)
3090     DrawTouchInputOverlay_ShowGrid(alpha);
3091
3092   DrawTouchInputOverlay_ShowGridButtons(alpha);
3093 }
3094
3095 static void DrawTouchGadgetsOverlay(void)
3096 {
3097   DrawGadgets_OverlayTouchButtons();
3098 }
3099 #endif