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