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