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