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