1 /*  RetroArch - A frontend for libretro.
2  *  Copyright (C) 2011-2021 - Daniel De Matteis
3  *  Copyright (C) 2019-2021 - James Leaver
4  *
5  *  RetroArch is free software: you can redistribute it and/or modify it under the terms
6  *  of the GNU General Public License as published by the Free Software Found-
7  *  ation, either version 3 of the License, or (at your option) any later version.
8  *
9  *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10  *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
11  *  PURPOSE.  See the GNU General Public License for more details.
12  *
13  *  You should have received a copy of the GNU General Public License along with RetroArch.
14  *  If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include <stdlib.h>
18 #include <boolean.h>
19 
20 #include <string/stdstring.h>
21 #include <file/file_path.h>
22 #include <retro_inline.h>
23 
24 #include "../verbosity.h"
25 
26 #if defined(HAVE_CONFIG_H)
27 #include "../config.h"
28 #endif
29 
30 #include "menu_screensaver.h"
31 
32 #ifndef PI
33 #define PI 3.14159265359f
34 #endif
35 
36 /* Font path defines */
37 #define MENU_SS_PKG_DIR "pkg"
38 #define MENU_SS_FONT_FILE "osd-font.ttf"
39 
40 /* Determines whether current platform has
41  * Unicode character support */
42 #if defined(HAVE_FREETYPE) || (defined(__APPLE__) && defined(HAVE_CORETEXT)) || (defined(HAVE_STB_FONT) && (defined(VITA) || defined(WIIU) || defined(ANDROID) || (defined(_WIN32) && !defined(_XBOX) && !defined(_MSC_VER) && _MSC_VER >= 1400) || (defined(_WIN32) && !defined(_XBOX) && defined(_MSC_VER)) || defined(HAVE_LIBNX) || defined(__linux__) || defined (HAVE_EMSCRIPTEN) || defined(__APPLE__) || defined(HAVE_ODROIDGO2) || defined(__PS3__)))
43 #define MENU_SS_UNICODE_ENABLED true
44 #else
45 #define MENU_SS_UNICODE_ENABLED false
46 #endif
47 
48 /* 256 on-screen particles provides a good
49  * balance between effect density and draw
50  * performance */
51 #define MENU_SS_NUM_PARTICLES 256
52 
53 /* Particle effect animations update at a base rate
54  * of 60Hz (-> 16.666 ms update period) */
55 #define MENU_SS_EFFECT_PERIOD ((1.0f / 60.0f) * 1000.0f)
56 
57 /* To produce a sharp image, font glyphs must
58  * be oversized and scaled down. Requested font
59  * size is:
60  *   (smallest screen dimension) * MENU_SS_FONT_SIZE_FACTOR */
61 #define MENU_SS_FONT_SIZE_FACTOR 0.1f
62 
63 /* On a 240p display, base particle size should
64  * be 4 pixels. To achieve this, we apply a global
65  * scaling factor of:
66  *   ((smallest screen dimension) * MENU_SS_PARTICLE_SIZE_FACTOR) / (font size) */
67 #define MENU_SS_PARTICLE_SIZE_FACTOR (4.0f / 240.0f)
68 
69 /* Produces a colour value in RGBA32 format
70  * from the specified 'tint' adjusted by the
71  * specified 'luminosity'
72  * > lum must not exceed 1.0f */
73 #define MENU_SS_PARTICLE_COLOR(tint_r, tint_g, tint_b, lum) (((uint32_t)(tint_r * lum) << 24) | ((uint32_t)(tint_g * lum) << 16) | ((uint32_t)(tint_b * lum) << 8) | 0xFF)
74 
75 /* Definition of screensaver 'particle':
76  * - symbol:          string representation of a font glyph
77  * - x:               centre x-coordinate of draw position
78  * - y:               centre y-coordinate of draw position
79  * - size:            glyph scale factor when drawn
80  *                    (1.0 == default size)
81  * - a,b,c,d:         general purpose effect-specific variables
82  *                    (e.g. velocity, radius, theta, ...)
83  * - color:           particle colour in RGBA32 format */
84 typedef struct
85 {
86    uint32_t color;
87    float x;
88    float y;
89    float size;
90    float a;
91    float b;
92    float c;
93    float d;
94    const char *symbol;
95 } menu_ss_particle_t;
96 
97 /* Holds all objects + metadata corresponding
98  * to a font */
99 typedef struct
100 {
101    font_data_t *font;
102    video_font_raster_block_t raster_block; /* ptr alignment */
103    float y_centre_offset;
104 } menu_ss_font_data_t;
105 
106 /* Holds values used to colourise a particle */
107 typedef struct
108 {
109    uint32_t color;
110    uint32_t r;
111    uint32_t g;
112    uint32_t b;
113 } menu_ss_particle_tint_t;
114 
115 struct menu_ss_handle
116 {
117    float bg_color[16];
118    menu_ss_font_data_t font_data;
119    unsigned last_width;
120    unsigned last_height;
121    float font_size;
122    float particle_scale;
123    menu_ss_particle_tint_t particle_tint;
124    menu_ss_particle_t *particles;
125    enum menu_screensaver_effect effect;
126    bool font_enabled;
127    bool unicode_enabled;
128 };
129 
130 /* UTF-8 Symbols */
131 static const char * const menu_ss_fallback_symbol = "*";
132 
133 #define MENU_SS_NUM_SNOW_SYMBOLS 3
134 static const char * const menu_ss_snow_symbols[] = {
135    "\xE2\x9D\x84", /* Snowflake, U+2744 */
136    "\xE2\x9D\x85", /* Tight Trifoliate Snowflake, U+2745 */
137    "\xE2\x9D\x86"  /* Heavy Chevron Snowflake, U+2746 */
138 };
139 
140 #define MENU_SS_NUM_STARFIELD_SYMBOLS 8
141 static const char * const menu_ss_starfield_symbols[] = {
142    "\xE2\x98\x85", /* Black Star, U+2605 */
143    "\xE2\x9C\xA6", /* Black Four Pointed Star, U+2726 */
144    "\xE2\x9C\xB4", /* Eight Pointed Black Star, U+2734 */
145    "\xE2\x9C\xB6", /* Six Pointed Black Star, U+2736 */
146    "\xE2\x9C\xB7", /* Eight Pointed Rectilinear Black Star, U+2737 */
147    "\xE2\x9C\xB8", /* Heavy Eight Pointed Rectilinear Black Star, U+2738 */
148    "\xE2\x9C\xB9", /* Twelve Pointed Black Star, U+2739 */
149    "\xE2\x97\x8F"  /* Black Circle, U+25CF */
150 };
151 
152 #define MENU_SS_NUM_VORTEX_SYMBOLS 6
153 static const char * const menu_ss_vortex_symbols[] = {
154    "\xE2\x9D\x8B", /* Heavy Eight Teardrop-Spoked Propeller Asterisk, U+274B */
155    "\xE2\x9C\xBD", /* Heavy Teardrop-Spoked Asterisk, U+273D */
156    "\xE2\x9C\xBB", /* Teardrop-Spoked Asterisk, U+273B */
157    "\xE2\x97\x89", /* Fisheye, U+25C9 */
158    "\xE2\x97\x8F", /* Black Circle, U+25CF */
159    "\xE2\x97\x86"  /* Black Diamond, U+25C6 */
160 };
161 
162 /******************/
163 /* Initialisation */
164 /******************/
165 
166 /* Creates and initialises a new screensaver object.
167  * Returned object must be freed using
168  * menu_screensaver_free().
169  * Returns NULL in the event of an error. */
menu_screensaver_init(void)170 menu_screensaver_t *menu_screensaver_init(void)
171 {
172    menu_screensaver_t *screensaver = NULL;
173    /* Screensaver background must be pure black,
174     * 100% opacity */
175    float bg_color[16]              = COLOR_HEX_TO_FLOAT(0x000000, 1.0f);
176 
177    /* Create menu_screensaver_t object */
178    screensaver = (menu_screensaver_t*)malloc(sizeof(*screensaver));
179 
180    if (!screensaver)
181       return NULL;
182 
183    /* Initial effect is always 'blank' */
184    screensaver->effect = MENU_SCREENSAVER_BLANK;
185 
186    /* > Fonts must be enabled for the screensaver to
187     *   function. 'font_enabled' flag exists purely
188     *   to prevent re-initialisation spam in the event
189     *   that font creation fails
190     * > Initialise 'Unicode enabled' state based on
191     *   compiler flags */
192    screensaver->font_enabled    = true;
193    screensaver->unicode_enabled = MENU_SS_UNICODE_ENABLED;
194 
195    /* Set background colour */
196    memcpy(screensaver->bg_color, bg_color, sizeof(screensaver->bg_color));
197 
198    /* Font is loaded on-demand
199     * (Don't waste memory unless we are actually
200     *  drawing an effect) */
201    memset(&screensaver->font_data, 0, sizeof(screensaver->font_data));
202 
203    /* Particle array is created on-demand
204     * (Don't waste memory unless we are actually
205     *  drawing an effect) */
206    screensaver->particles           = NULL;
207    screensaver->particle_tint.color = 0;
208    screensaver->particle_tint.r     = 0;
209    screensaver->particle_tint.g     = 0;
210    screensaver->particle_tint.b     = 0;
211 
212    /* Initial dimensions are zeroed out - will be set
213     * on first call of menu_screensaver_iterate() */
214    screensaver->last_width     = 0;
215    screensaver->last_height    = 0;
216    screensaver->font_size      = 0.0f;
217    screensaver->particle_scale = 0.0f;
218 
219    return screensaver;
220 }
221 
222 /* Frees specified screensaver object */
menu_screensaver_free(menu_screensaver_t * screensaver)223 void menu_screensaver_free(menu_screensaver_t *screensaver)
224 {
225    if (!screensaver)
226       return;
227 
228    /* Free font */
229    if (screensaver->font_data.font)
230    {
231       gfx_display_font_free(screensaver->font_data.font);
232       video_coord_array_free(&screensaver->font_data.raster_block.carr);
233       screensaver->font_data.font = NULL;
234 
235       font_driver_bind_block(NULL, NULL);
236    }
237 
238    /* Free particle array */
239    if (screensaver->particles)
240       free(screensaver->particles);
241    screensaver->particles = NULL;
242 
243    free(screensaver);
244 }
245 
246 /*********************/
247 /* Context functions */
248 /*********************/
249 
250 /* Called when the graphics context is destroyed
251  * or reset (a dedicated 'reset' function is
252  * unnecessary) */
menu_screensaver_context_destroy(menu_screensaver_t * screensaver)253 void menu_screensaver_context_destroy(menu_screensaver_t *screensaver)
254 {
255    if (!screensaver)
256       return;
257 
258    /* Free any existing font
259     * (will be recreated, if required, on the next
260     * call of menu_screensaver_iterate()) */
261    if (screensaver->font_data.font)
262    {
263       gfx_display_font_free(screensaver->font_data.font);
264       video_coord_array_free(&screensaver->font_data.raster_block.carr);
265       screensaver->font_data.font = NULL;
266    }
267 }
268 
269 /**********************/
270 /* Run loop functions */
271 /**********************/
272 
273 /* qsort() helpers */
menu_ss_starfield_qsort_func(const menu_ss_particle_t * a,const menu_ss_particle_t * b)274 static int menu_ss_starfield_qsort_func(const menu_ss_particle_t *a,
275       const menu_ss_particle_t *b)
276 {
277    return a->c > b->c ? -1 : 1;
278 }
279 
menu_ss_vortex_qsort_func(const menu_ss_particle_t * a,const menu_ss_particle_t * b)280 static int menu_ss_vortex_qsort_func(const menu_ss_particle_t *a,
281       const menu_ss_particle_t *b)
282 {
283    return a->a < b->a ? -1 : 1;
284 }
285 
286 /* Calculates base font size and particle
287  * scale based on current screen dimensions */
menu_screensaver_set_dimensions(menu_screensaver_t * screensaver,unsigned width,unsigned height)288 static INLINE void menu_screensaver_set_dimensions(
289       menu_screensaver_t *screensaver,
290       unsigned width, unsigned height)
291 {
292    float screen_size           = (float)((width < height) ? width : height);
293    screensaver->font_size      = (screen_size * MENU_SS_FONT_SIZE_FACTOR) + 0.5f;
294    screensaver->particle_scale = (screen_size * MENU_SS_PARTICLE_SIZE_FACTOR) / screensaver->font_size;
295    screensaver->last_width     = width;
296    screensaver->last_height    = height;
297 }
298 
menu_screensaver_init_effect(menu_screensaver_t * screensaver)299 static bool menu_screensaver_init_effect(menu_screensaver_t *screensaver)
300 {
301    unsigned width;
302    unsigned height;
303    size_t i;
304 
305    /* Create particle array, if required */
306    if (!screensaver->particles)
307    {
308       screensaver->particles = (menu_ss_particle_t*)
309             calloc(MENU_SS_NUM_PARTICLES, sizeof(*screensaver->particles));
310 
311       if (!screensaver->particles)
312          return false;
313    }
314 
315    width  = screensaver->last_width;
316    height = screensaver->last_height;
317 
318    /* Initialise array */
319    switch (screensaver->effect)
320    {
321       case MENU_SCREENSAVER_SNOW:
322          {
323             for (i = 0; i < MENU_SS_NUM_PARTICLES; i++)
324             {
325                menu_ss_particle_t *particle = &screensaver->particles[i];
326                float size_factor;
327 
328                particle->x    = (float)(rand() % width);
329                particle->y    = (float)(rand() % height);
330                particle->a    = (float)(rand() % 64 - 16) * 0.1f;
331                particle->b    = (float)(rand() % 64 - 48) * 0.1f;
332 
333                /* Get particle size */
334                size_factor    = (float)i / (float)MENU_SS_NUM_PARTICLES;
335                size_factor    = size_factor * size_factor;
336                particle->size = 1.0f + (size_factor * 2.0f);
337 
338                /* If Unicode is supported, select a random
339                 * snowflake symbol */
340                particle->symbol    = menu_ss_fallback_symbol;
341                if (screensaver->unicode_enabled)
342                   particle->symbol = menu_ss_snow_symbols[(unsigned)(rand() % MENU_SS_NUM_SNOW_SYMBOLS)];
343             }
344          }
345          break;
346       case MENU_SCREENSAVER_STARFIELD:
347          {
348             float max_depth            = (float)(width > height ? width : height);
349             float initial_speed_factor = 0.02f * max_depth / 240.0f;
350 
351             for (i = 0; i < MENU_SS_NUM_PARTICLES; i++)
352             {
353                menu_ss_particle_t *particle = &screensaver->particles[i];
354 
355                /* x pos ('physical' space) */
356                particle->a = (float)(rand() % width);
357                /* y pos ('physical' space) */
358                particle->b = (float)(rand() % height);
359                /* depth */
360                particle->c = max_depth;
361                /* speed */
362                particle->d = 1.0f + ((float)(rand() % 20) * initial_speed_factor);
363 
364                /* If Unicode is supported, select a random
365                 * star symbol */
366                particle->symbol    = menu_ss_fallback_symbol;
367                if (screensaver->unicode_enabled)
368                   particle->symbol = menu_ss_starfield_symbols[(unsigned)(rand() % MENU_SS_NUM_STARFIELD_SYMBOLS)];
369             }
370          }
371          break;
372       case MENU_SCREENSAVER_VORTEX:
373          {
374             float min_screen_dimension = (float)(width < height ? width : height);
375             float max_radius           = (float)sqrt((double)((width * width) + (height * height))) / 2.0f;
376             float radial_speed_factor  = 0.001f * min_screen_dimension / 240.0f;
377 
378             for (i = 0; i < MENU_SS_NUM_PARTICLES; i++)
379             {
380                menu_ss_particle_t *particle = &screensaver->particles[i];
381 
382                /* radius */
383                particle->a = 1.0f + (((float)rand() / (float)RAND_MAX) * max_radius);
384                /* theta */
385                particle->b = ((float)rand() / (float)RAND_MAX) * 2.0f * PI;
386                /* radial speed */
387                particle->c = (float)((rand() % 100) + 1) * radial_speed_factor;
388                /* rotational speed */
389                particle->d = (((float)((rand() % 50) + 1) * 0.005f) + 0.1f) * (PI / 360.0f);
390 
391                /* If Unicode is supported, select a random
392                 * star symbol */
393                particle->symbol    = menu_ss_fallback_symbol;
394                if (screensaver->unicode_enabled)
395                   particle->symbol = menu_ss_vortex_symbols[(unsigned)(rand() % MENU_SS_NUM_VORTEX_SYMBOLS)];
396             }
397          }
398          break;
399       default:
400          /* Error condition - do nothing */
401          return false;
402    }
403 
404    return true;
405 }
406 
407 /* Checks for and applies any pending screensaver
408  * state changes */
menu_screensaver_update_state(menu_screensaver_t * screensaver,gfx_display_t * p_disp,enum menu_screensaver_effect effect,uint32_t particle_tint,unsigned width,unsigned height,const char * dir_assets)409 static bool menu_screensaver_update_state(
410       menu_screensaver_t *screensaver, gfx_display_t *p_disp,
411       enum menu_screensaver_effect effect, uint32_t particle_tint,
412       unsigned width, unsigned height, const char *dir_assets)
413 {
414    bool init_effect = false;
415 
416 #if defined(_3DS)
417    /* 3DS has an 'incomplete' font driver,
418     * and cannot render screensaver effects */
419    effect = MENU_SCREENSAVER_BLANK;
420 #endif
421 
422    /* Check if dimensions have changed */
423    if ((screensaver->last_width  != width) ||
424        (screensaver->last_height != height))
425    {
426       menu_screensaver_set_dimensions(screensaver, width, height);
427 
428       /* Free any existing font */
429       if (screensaver->font_data.font)
430       {
431          gfx_display_font_free(screensaver->font_data.font);
432          video_coord_array_free(&screensaver->font_data.raster_block.carr);
433          screensaver->font_data.font = NULL;
434       }
435 
436       init_effect = true;
437    }
438 
439    /* Check if effect has changed */
440    if (screensaver->effect != effect)
441    {
442       screensaver->effect = effect;
443       init_effect         = true;
444    }
445 
446    /* Check if particle tint has changed */
447    if (screensaver->particle_tint.color != particle_tint)
448    {
449       screensaver->particle_tint.color = particle_tint;
450       screensaver->particle_tint.r     = (particle_tint >> 16) & 0xFF;
451       screensaver->particle_tint.g     = (particle_tint >>  8) & 0xFF;
452       screensaver->particle_tint.b     = (particle_tint      ) & 0xFF;
453    }
454 
455    /* Create font, if required */
456    if ((screensaver->effect != MENU_SCREENSAVER_BLANK) &&
457        !screensaver->font_data.font &&
458        screensaver->font_enabled)
459    {
460 #if defined(HAVE_FREETYPE) || (defined(__APPLE__) && defined(HAVE_CORETEXT)) || defined(HAVE_STB_FONT)
461       char font_file[PATH_MAX_LENGTH];
462       char pkg_path[PATH_MAX_LENGTH];
463 
464       font_file[0] = '\0';
465       pkg_path[0]  = '\0';
466 
467       /* Get font file path */
468       if (!string_is_empty(dir_assets))
469          fill_pathname_join(pkg_path, dir_assets, MENU_SS_PKG_DIR, sizeof(pkg_path));
470       else
471          strlcpy(pkg_path, MENU_SS_PKG_DIR, sizeof(pkg_path));
472 
473       fill_pathname_join(font_file, pkg_path, MENU_SS_FONT_FILE,
474             sizeof(font_file));
475 
476       /* Warn if font file is missing */
477       if (!path_is_valid(font_file))
478       {
479          RARCH_WARN("[Menu Screensaver] Font asset missing: %s\n", font_file);
480          screensaver->unicode_enabled = false;
481       }
482 #else
483       /* On platforms without TTF support, there is
484        * no need to generate a font path (a bitmap
485        * font will be created automatically) */
486       char font_file[PATH_MAX_LENGTH];
487       font_file[0] = '\0';
488 #endif
489 
490       /* Create font */
491       screensaver->font_data.font = gfx_display_font_file(p_disp,
492             font_file, screensaver->font_size,
493             video_driver_is_threaded());
494 
495       /* If font was created successfully, fetch metadata */
496       if (screensaver->font_data.font)
497          screensaver->font_data.y_centre_offset =
498                (float)font_driver_get_line_centre_offset(
499                      screensaver->font_data.font, 1.0f);
500       /* In case of error, warn and disable
501        * further attempts to create fonts */
502       else
503       {
504          RARCH_WARN("[Menu Screensaver] Failed to initialise font - animation disabled\n");
505          screensaver->font_enabled = false;
506          return false;
507       }
508    }
509 
510    /* Initialise animation effect, if required */
511    if (init_effect)
512       return menu_screensaver_init_effect(screensaver);
513 
514    return true;
515 }
516 
517 /* Processes screensaver animation logic
518  * Called every frame on the main thread */
menu_screensaver_iterate(menu_screensaver_t * screensaver,gfx_display_t * p_disp,gfx_animation_t * p_anim,enum menu_screensaver_effect effect,float effect_speed,uint32_t particle_tint,unsigned width,unsigned height,const char * dir_assets)519 void menu_screensaver_iterate(
520       menu_screensaver_t *screensaver,
521       gfx_display_t *p_disp, gfx_animation_t *p_anim,
522       enum menu_screensaver_effect effect, float effect_speed,
523       uint32_t particle_tint, unsigned width, unsigned height,
524       const char *dir_assets)
525 {
526    float base_particle_size;
527    uint32_t tint_r;
528    uint32_t tint_g;
529    uint32_t tint_b;
530    float global_speed_factor;
531    size_t i;
532 
533    if (!screensaver)
534       return;
535 
536    /* Apply pending state changes */
537    if (!menu_screensaver_update_state(
538          screensaver, p_disp,
539          effect, particle_tint,
540          width, height, dir_assets) ||
541        (screensaver->effect == MENU_SCREENSAVER_BLANK) ||
542        !screensaver->particles)
543       return;
544 
545    base_particle_size = screensaver->particle_scale * screensaver->font_size;
546    tint_r             = screensaver->particle_tint.r;
547    tint_g             = screensaver->particle_tint.g;
548    tint_b             = screensaver->particle_tint.b;
549 
550    /* Set global animation speed */
551    global_speed_factor = p_anim->delta_time / MENU_SS_EFFECT_PERIOD;
552    if (effect_speed > 0.0001f)
553       global_speed_factor *= effect_speed;
554 
555    /* Update particle array */
556    switch (screensaver->effect)
557    {
558       case MENU_SCREENSAVER_SNOW:
559          for (i = 0; i < MENU_SS_NUM_PARTICLES; i++)
560          {
561             menu_ss_particle_t *particle = &screensaver->particles[i];
562             float particle_size_px       = particle->size * base_particle_size;
563             bool update_symbol           = false;
564             float luminosity;
565 
566             /* Update particle 'speed' */
567             particle->a = particle->a + (float)(rand() % 16 - 9) * 0.01f;
568             particle->b = particle->b + (float)(rand() % 16 - 7) * 0.01f;
569 
570             particle->a = (particle->a < -0.4f) ? -0.4f : particle->a;
571             particle->a = (particle->a >  0.1f) ?  0.1f : particle->a;
572 
573             particle->b = (particle->b < -0.1f) ? -0.1f : particle->b;
574             particle->b = (particle->b >  0.4f) ?  0.4f : particle->b;
575 
576             /* Update particle location */
577             particle->x = particle->x + (global_speed_factor * particle->size * particle->a);
578             particle->y = particle->y + (global_speed_factor * particle->size * particle->b);
579 
580             /* Get particle colour */
581             luminosity      = 0.5f + (particle->size / 6.0f);
582             particle->color = MENU_SS_PARTICLE_COLOR(tint_r, tint_g, tint_b, luminosity);
583 
584             /* Reset particle if it has fallen off screen */
585             if (particle->x < -particle_size_px)
586             {
587                particle->x   = (float)width + particle_size_px;
588                update_symbol = true;
589             }
590 
591             if (particle->y > (float)height + particle_size_px)
592             {
593                particle->y   = -particle_size_px;
594                update_symbol = true;
595             }
596 
597             if (update_symbol && screensaver->unicode_enabled)
598                particle->symbol = menu_ss_snow_symbols[(unsigned)(rand() % MENU_SS_NUM_SNOW_SYMBOLS)];
599          }
600          break;
601       case MENU_SCREENSAVER_STARFIELD:
602          {
603             float max_depth            = (float)(width > height ? width : height);
604             float initial_speed_factor = 0.02f * max_depth / 240.0f;
605             float focal_length         = max_depth * 2.0f;
606             float x_centre             = (float)(width >> 1);
607             float y_centre             = (float)(height >> 1);
608             float particle_size_px;
609             float luminosity;
610 
611             /* Based on an example found here:
612              * https://codepen.io/nodws/pen/pejBNb */
613             for (i = 0; i < MENU_SS_NUM_PARTICLES; i++)
614             {
615                menu_ss_particle_t *particle = &screensaver->particles[i];
616 
617                /* Get particle size */
618                particle->size   = focal_length / (2.0f * particle->c);
619                particle_size_px = particle->size * base_particle_size;
620 
621                /* Update depth */
622                particle->c -= particle->d * global_speed_factor;
623 
624                /* Reset particle if it has:
625                 * - Dropped off the edge of the screen
626                 * - Reached the screen depth */
627                if ((particle->x < -particle_size_px) ||
628                    (particle->x > (float)width + particle_size_px) ||
629                    (particle->y < -particle_size_px) ||
630                    (particle->y > (float)height + particle_size_px) ||
631                    (particle->c <= 0.0f))
632                {
633                   /* x pos ('physical' space) */
634                   particle->a = (float)(rand() % width);
635                   /* y pos ('physical' space) */
636                   particle->b = (float)(rand() % height);
637                   /* depth */
638                   particle->c = max_depth;
639                   /* speed */
640                   particle->d = 1.0f + ((float)(rand() % 20) * initial_speed_factor);
641 
642                   /* Reset size */
643                   particle->size = 1.0f;
644 
645                   /* If Unicode is supported, select a random
646                    * star symbol */
647                   if (screensaver->unicode_enabled)
648                      particle->symbol = menu_ss_starfield_symbols[(unsigned)(rand() % MENU_SS_NUM_STARFIELD_SYMBOLS)];
649                }
650 
651                /* Get particle location */
652                particle->x = (particle->a - x_centre) * (focal_length / particle->c);
653                particle->x += x_centre;
654 
655                particle->y = (particle->b - y_centre) * (focal_length / particle->c);
656                particle->y += y_centre;
657 
658                /* Get particle colour */
659                luminosity      = 0.25f + (0.75f - (particle->c / max_depth) * 0.75f);
660                particle->color = MENU_SS_PARTICLE_COLOR(tint_r, tint_g, tint_b, luminosity);
661             }
662 
663             /* Particles must be drawn in order of depth,
664              * from furthest away to nearest */
665             qsort(screensaver->particles,
666                   MENU_SS_NUM_PARTICLES, sizeof(menu_ss_particle_t),
667                   (int (*)(const void *, const void *))menu_ss_starfield_qsort_func);
668          }
669          break;
670       case MENU_SCREENSAVER_VORTEX:
671          {
672             float min_screen_dimension = (float)(width < height ? width : height);
673             float max_radius           = (float)sqrt((double)((width * width) + (height * height))) / 2.0f;
674             float radial_speed_factor  = 0.001f * min_screen_dimension / 240.0f;
675             float x_centre             = (float)(width >> 1);
676             float y_centre             = (float)(height >> 1);
677             float r_speed;
678             float theta_speed;
679             float size_factor;
680             float luminosity;
681 
682             for (i = 0; i < MENU_SS_NUM_PARTICLES; i++)
683             {
684                menu_ss_particle_t *particle = &screensaver->particles[i];
685 
686                /* Update particle speed */
687                r_speed     = particle->c * global_speed_factor;
688                theta_speed = particle->d * global_speed_factor;
689                if ((particle->a > 0.0f) && (particle->a < min_screen_dimension))
690                {
691                   float base_scale_factor = (min_screen_dimension - particle->a) / min_screen_dimension;
692                   r_speed     *= 1.0f + (base_scale_factor * 8.0f);
693                   theta_speed *= 1.0f + (base_scale_factor * base_scale_factor * 6.0f);
694                }
695                particle->a -= r_speed;
696                particle->b += theta_speed;
697 
698                /* Reset particle if it has reached the centre of the screen */
699                if (particle->a < 0.0f)
700                {
701                   /* radius
702                    * Note: In theory, this should be:
703                    * > particle->a = max_radius;
704                    * ...but it turns out that spawning new particles at random
705                    * locations produces a more visually appealing result... */
706                   particle->a = 1.0f + (((float)rand() / (float)RAND_MAX) * max_radius);
707                   /* theta */
708                   particle->b = ((float)rand() / (float)RAND_MAX) * 2.0f * PI;
709                   /* radial speed */
710                   particle->c = (float)((rand() % 100) + 1) * radial_speed_factor;
711                   /* rotational speed */
712                   particle->d = (((float)((rand() % 50) + 1) * 0.005f) + 0.1f) * (PI / 360.0f);
713 
714                   /* If Unicode is supported, select a random
715                    * star symbol */
716                   if (screensaver->unicode_enabled)
717                      particle->symbol = menu_ss_vortex_symbols[(unsigned)(rand() % MENU_SS_NUM_VORTEX_SYMBOLS)];
718                }
719 
720                /* Get particle location */
721                particle->x = (particle->a * cos(particle->b)) + x_centre;
722                particle->y = (particle->a * sin(particle->b)) + y_centre;
723 
724                /* Get particle size */
725                size_factor    = 1.0f - ((max_radius - particle->a) / max_radius);
726                particle->size = sqrt(size_factor) * 2.5f;
727 
728                /* Get particle colour */
729                luminosity      = 0.2f + (0.8f - size_factor * 0.8f);
730                particle->color = MENU_SS_PARTICLE_COLOR(tint_r, tint_g, tint_b, luminosity);
731             }
732 
733             /* Particles must be drawn in order of radius;
734              * particles closest to the centre are further away
735              * from the screen, and must be drawn first */
736             qsort(screensaver->particles,
737                   MENU_SS_NUM_PARTICLES, sizeof(menu_ss_particle_t),
738                   (int (*)(const void *, const void *))menu_ss_vortex_qsort_func);
739          }
740          break;
741       default:
742          /* Error condition - do nothing */
743          break;
744    }
745 }
746 
747 /* Draws screensaver
748  * Called every frame (on the video thread,
749  * if threaded video is on) */
menu_screensaver_frame(menu_screensaver_t * screensaver,video_frame_info_t * video_info,gfx_display_t * p_disp)750 void menu_screensaver_frame(menu_screensaver_t *screensaver,
751       video_frame_info_t *video_info, gfx_display_t *p_disp)
752 {
753    void *userdata = NULL;
754    unsigned video_width;
755    unsigned video_height;
756 
757    if (!screensaver)
758       return;
759 
760    video_width  = video_info->width;
761    video_height = video_info->height;
762    userdata     = video_info->userdata;
763 
764    /* Set viewport */
765    video_driver_set_viewport(video_width, video_height, true, false);
766 
767    /* Draw background */
768    gfx_display_draw_quad(
769          p_disp,
770          userdata,
771          video_width,
772          video_height,
773          0, 0,
774          screensaver->last_width, screensaver->last_height,
775          screensaver->last_width, screensaver->last_height,
776          screensaver->bg_color);
777 
778    /* Draw particle effect, if required */
779    if ((screensaver->effect != MENU_SCREENSAVER_BLANK) &&
780        screensaver->font_data.font &&
781        screensaver->particles)
782    {
783       font_data_t *font     = screensaver->font_data.font;
784       float y_centre_offset = screensaver->font_data.y_centre_offset;
785       float particle_scale  = screensaver->particle_scale;
786       size_t i;
787 
788       /* Bind font */
789       font_driver_bind_block(font, &screensaver->font_data.raster_block);
790       screensaver->font_data.raster_block.carr.coords.vertices = 0;
791 
792       /* Render text */
793       for (i = 0; i < MENU_SS_NUM_PARTICLES; i++)
794       {
795          menu_ss_particle_t *particle = &screensaver->particles[i];
796 
797          gfx_display_draw_text(
798                font,
799                particle->symbol,
800                particle->x,
801                particle->y + y_centre_offset,
802                video_width, video_height,
803                particle->color,
804                TEXT_ALIGN_CENTER,
805                particle->size * particle_scale,
806                false, 0.0f, true);
807       }
808 
809       /* Flush text and unbind font */
810       if (screensaver->font_data.raster_block.carr.coords.vertices != 0)
811       {
812          font_driver_flush(video_width, video_height, font);
813          screensaver->font_data.raster_block.carr.coords.vertices = 0;
814       }
815       font_driver_bind_block(font, NULL);
816    }
817 
818    /* Unset viewport */
819    video_driver_set_viewport(video_width, video_height, false, true);
820 }
821