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