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