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