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