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