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