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