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