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