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