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