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