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