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