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