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