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