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