1 /* Copyright  (C) 2010-2019 The RetroArch team
2  *
3  * ---------------------------------------------------------------------------------------
4  * The following license statement only applies to this file (gfx_thumbnail.c).
5  * ---------------------------------------------------------------------------------------
6  *
7  * Permission is hereby granted, free of charge,
8  * to any person obtaining a copy of this software and associated documentation files (the "Software"),
9  * to deal in the Software without restriction, including without limitation the rights to
10  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11  * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include <stdlib.h>
24 #include <string.h>
25 #include <ctype.h>
26 
27 #include <features/features_cpu.h>
28 #include <file/file_path.h>
29 #include <string/stdstring.h>
30 
31 #include "gfx_display.h"
32 #include "gfx_animation.h"
33 
34 #include "gfx_thumbnail.h"
35 
36 #include "../tasks/tasks_internal.h"
37 
38 #define DEFAULT_GFX_THUMBNAIL_STREAM_DELAY  83.333333f
39 #define DEFAULT_GFX_THUMBNAIL_FADE_DURATION 166.66667f
40 
41 /* Utility structure, sent as userdata when pushing
42  * an image load */
43 typedef struct
44 {
45    uint64_t list_id;
46    gfx_thumbnail_t *thumbnail;
47 } gfx_thumbnail_tag_t;
48 
49 /* Setters */
50 
51 /* When streaming thumbnails, sets time in ms that an
52  * entry must be on screen before an image load is
53  * requested
54  * > if 'delay' is negative, default value is set */
gfx_thumbnail_set_stream_delay(float delay)55 void gfx_thumbnail_set_stream_delay(float delay)
56 {
57    gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr();
58 
59    p_gfx_thumb->stream_delay = (delay >= 0.0f) ?
60          delay : DEFAULT_GFX_THUMBNAIL_STREAM_DELAY;
61 }
62 
63 /* Sets duration in ms of the thumbnail 'fade in'
64  * animation
65  * > If 'duration' is negative, default value is set */
gfx_thumbnail_set_fade_duration(float duration)66 void gfx_thumbnail_set_fade_duration(float duration)
67 {
68    gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr();
69 
70    p_gfx_thumb->fade_duration = (duration >= 0.0f) ?
71          duration : DEFAULT_GFX_THUMBNAIL_FADE_DURATION;
72 }
73 
74 /* Specifies whether 'fade in' animation should be
75  * triggered for missing thumbnails
76  * > When 'true', allows menu driver to animate
77  *   any 'thumbnail unavailable' notifications */
gfx_thumbnail_set_fade_missing(bool fade_missing)78 void gfx_thumbnail_set_fade_missing(bool fade_missing)
79 {
80    gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr();
81 
82    p_gfx_thumb->fade_missing = fade_missing;
83 }
84 
85 /* Callbacks */
86 
87 /* Fade animation callback - simply resets thumbnail
88  * 'fade_active' status */
gfx_thumbnail_fade_cb(void * userdata)89 static void gfx_thumbnail_fade_cb(void *userdata)
90 {
91    gfx_thumbnail_t *thumbnail = (gfx_thumbnail_t*)userdata;
92 
93    if (!thumbnail)
94       return;
95 
96    thumbnail->fade_active = false;
97 }
98 
99 /* Initialises thumbnail 'fade in' animation */
gfx_thumbnail_init_fade(gfx_thumbnail_state_t * p_gfx_thumb,gfx_thumbnail_t * thumbnail)100 static void gfx_thumbnail_init_fade(
101       gfx_thumbnail_state_t *p_gfx_thumb,
102       gfx_thumbnail_t *thumbnail)
103 {
104    /* Sanity check */
105    if (!thumbnail)
106       return;
107 
108    /* A 'fade in' animation is triggered if:
109     * - Thumbnail is available
110     * - Thumbnail is missing and 'fade_missing' is enabled */
111    if ((thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE) ||
112        (p_gfx_thumb->fade_missing &&
113             (thumbnail->status == GFX_THUMBNAIL_STATUS_MISSING)))
114    {
115       if (p_gfx_thumb->fade_duration > 0.0f)
116       {
117          gfx_animation_ctx_entry_t animation_entry;
118 
119          thumbnail->alpha                 = 0.0f;
120          thumbnail->fade_active           = true;
121 
122          animation_entry.easing_enum      = EASING_OUT_QUAD;
123          animation_entry.tag              = (uintptr_t)&thumbnail->alpha;
124          animation_entry.duration         = p_gfx_thumb->fade_duration;
125          animation_entry.target_value     = 1.0f;
126          animation_entry.subject          = &thumbnail->alpha;
127          animation_entry.cb               = gfx_thumbnail_fade_cb;
128          animation_entry.userdata         = thumbnail;
129 
130          gfx_animation_push(&animation_entry);
131       }
132       else
133          thumbnail->alpha = 1.0f;
134    }
135 }
136 
137 /* Used to process thumbnail data following completion
138  * of image load task */
gfx_thumbnail_handle_upload(retro_task_t * task,void * task_data,void * user_data,const char * err)139 static void gfx_thumbnail_handle_upload(
140       retro_task_t *task, void *task_data, void *user_data, const char *err)
141 {
142    gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr();
143    struct texture_image *img          = (struct texture_image*)task_data;
144    gfx_thumbnail_tag_t *thumbnail_tag = (gfx_thumbnail_tag_t*)user_data;
145    bool fade_enabled                  = false;
146 
147    /* Sanity check */
148    if (!thumbnail_tag)
149       goto end;
150 
151    /* Ensure that we are operating on the correct
152     * thumbnail... */
153    if (thumbnail_tag->list_id != p_gfx_thumb->list_id)
154       goto end;
155 
156    /* Only process image if we are waiting for it */
157    if (thumbnail_tag->thumbnail->status != GFX_THUMBNAIL_STATUS_PENDING)
158       goto end;
159 
160    /* Sanity check: if thumbnail already has a texture,
161     * we're in some kind of weird error state - in this
162     * case, the best course of action is to just reset
163     * the thumbnail... */
164    if (thumbnail_tag->thumbnail->texture)
165       gfx_thumbnail_reset(thumbnail_tag->thumbnail);
166 
167    /* Set thumbnail 'missing' status by default
168     * (saves a number of checks later) */
169    thumbnail_tag->thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
170 
171    /* If we reach this stage, thumbnail 'fade in'
172     * animations should be applied (based on current
173     * thumbnail status and global configuration) */
174    fade_enabled = true;
175 
176    /* Check we have a valid image */
177    if (!img || (img->width < 1) || (img->height < 1))
178       goto end;
179 
180    /* Upload texture to GPU */
181    if (!video_driver_texture_load(
182             img, TEXTURE_FILTER_MIPMAP_LINEAR,
183             &thumbnail_tag->thumbnail->texture))
184       goto end;
185 
186    /* Cache dimensions */
187    thumbnail_tag->thumbnail->width  = img->width;
188    thumbnail_tag->thumbnail->height = img->height;
189 
190    /* Update thumbnail status */
191    thumbnail_tag->thumbnail->status = GFX_THUMBNAIL_STATUS_AVAILABLE;
192 
193 end:
194    /* Clean up */
195    if (img)
196    {
197       image_texture_free(img);
198       free(img);
199    }
200 
201    if (thumbnail_tag)
202    {
203       /* Trigger 'fade in' animation, if required */
204       if (fade_enabled)
205          gfx_thumbnail_init_fade(p_gfx_thumb,
206                thumbnail_tag->thumbnail);
207 
208       free(thumbnail_tag);
209    }
210 }
211 
212 /* Core interface */
213 
214 /* When called, prevents the handling of any pending
215  * thumbnail load requests
216  * >> **MUST** be called before deleting any gfx_thumbnail_t
217  *    objects passed to gfx_thumbnail_request() or
218  *    gfx_thumbnail_process_stream(), otherwise
219  *    heap-use-after-free errors *will* occur */
gfx_thumbnail_cancel_pending_requests(void)220 void gfx_thumbnail_cancel_pending_requests(void)
221 {
222    gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr();
223 
224    p_gfx_thumb->list_id++;
225 }
226 
227 /* Requests loading of the specified thumbnail
228  * - If operation fails, 'thumbnail->status' will be set to
229  *   GFX_THUMBNAIL_STATUS_MISSING
230  * - If operation is successful, 'thumbnail->status' will be
231  *   set to GFX_THUMBNAIL_STATUS_PENDING
232  * 'thumbnail' will be populated with texture info/metadata
233  * once the image load is complete
234  * NOTE 1: Must be called *after* gfx_thumbnail_set_system()
235  *         and gfx_thumbnail_set_content*()
236  * NOTE 2: 'playlist' and 'idx' are only required here for
237  *         on-demand thumbnail download support
238  *         (an annoyance...) */
gfx_thumbnail_request(gfx_thumbnail_path_data_t * path_data,enum gfx_thumbnail_id thumbnail_id,playlist_t * playlist,size_t idx,gfx_thumbnail_t * thumbnail,unsigned gfx_thumbnail_upscale_threshold,bool network_on_demand_thumbnails)239 void gfx_thumbnail_request(
240       gfx_thumbnail_path_data_t *path_data, enum gfx_thumbnail_id thumbnail_id,
241       playlist_t *playlist, size_t idx, gfx_thumbnail_t *thumbnail,
242       unsigned gfx_thumbnail_upscale_threshold,
243       bool network_on_demand_thumbnails
244       )
245 {
246    const char *thumbnail_path         = NULL;
247    bool has_thumbnail                 = false;
248    gfx_thumbnail_state_t *p_gfx_thumb = NULL;
249    p_gfx_thumb                        = NULL;
250 
251    if (!path_data || !thumbnail)
252       return;
253 
254    p_gfx_thumb                        = gfx_thumb_get_ptr();
255 
256    /* Reset thumbnail, then set 'missing' status by default
257     * (saves a number of checks later) */
258    gfx_thumbnail_reset(thumbnail);
259    thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
260 
261    /* Update/extract thumbnail path */
262    if (gfx_thumbnail_is_enabled(path_data, thumbnail_id))
263       if (gfx_thumbnail_update_path(path_data, thumbnail_id))
264          has_thumbnail = gfx_thumbnail_get_path(path_data, thumbnail_id, &thumbnail_path);
265 
266    /* Load thumbnail, if required */
267    if (has_thumbnail)
268    {
269       if (path_is_valid(thumbnail_path))
270       {
271          gfx_thumbnail_tag_t *thumbnail_tag =
272                (gfx_thumbnail_tag_t*)malloc(sizeof(gfx_thumbnail_tag_t));
273 
274          if (!thumbnail_tag)
275             goto end;
276 
277          /* Configure user data */
278          thumbnail_tag->thumbnail = thumbnail;
279          thumbnail_tag->list_id   = p_gfx_thumb->list_id;
280 
281          /* Would like to cancel any existing image load tasks
282           * here, but can't see how to do it... */
283          if (task_push_image_load(
284                thumbnail_path, video_driver_supports_rgba(),
285                gfx_thumbnail_upscale_threshold,
286                gfx_thumbnail_handle_upload, thumbnail_tag))
287             thumbnail->status = GFX_THUMBNAIL_STATUS_PENDING;
288       }
289 #ifdef HAVE_NETWORKING
290       /* Handle on demand thumbnail downloads */
291       else if (network_on_demand_thumbnails)
292       {
293          const char *system                         = NULL;
294          const char *img_name                       = NULL;
295          static char last_img_name[PATH_MAX_LENGTH] = {0};
296 
297          if (!playlist)
298             goto end;
299 
300          /* Get current image name */
301          if (!gfx_thumbnail_get_img_name(path_data, &img_name))
302             goto end;
303 
304          /* Only trigger a thumbnail download if image
305           * name has changed since the last call of
306           * gfx_thumbnail_request()
307           * > Allows gfx_thumbnail_request() to be used
308           *   for successive right/left thumbnail requests
309           *   with minimal duplication of effort
310           *   (i.e. task_push_pl_entry_thumbnail_download()
311           *   will automatically cancel if a download for the
312           *   existing playlist entry is pending, but the
313           *   checks required for this involve significant
314           *   overheads. We can avoid this entirely with
315           *   a simple string comparison) */
316          if (string_is_equal(img_name, last_img_name))
317             goto end;
318 
319          strlcpy(last_img_name, img_name, sizeof(last_img_name));
320 
321          /* Get system name */
322          if (!gfx_thumbnail_get_system(path_data, &system))
323             goto end;
324 
325          /* Trigger thumbnail download */
326          task_push_pl_entry_thumbnail_download(
327                system, playlist, (unsigned)idx,
328                false, true);
329       }
330 #endif
331    }
332 
333 end:
334    /* Trigger 'fade in' animation, if required */
335    if (thumbnail->status != GFX_THUMBNAIL_STATUS_PENDING)
336       gfx_thumbnail_init_fade(p_gfx_thumb,
337             thumbnail);
338 }
339 
340 /* Requests loading of a specific thumbnail image file
341  * (may be used, for example, to load savestate images)
342  * - If operation fails, 'thumbnail->status' will be set to
343  *   MUI_THUMBNAIL_STATUS_MISSING
344  * - If operation is successful, 'thumbnail->status' will be
345  *   set to MUI_THUMBNAIL_STATUS_PENDING
346  * 'thumbnail' will be populated with texture info/metadata
347  * once the image load is complete */
gfx_thumbnail_request_file(const char * file_path,gfx_thumbnail_t * thumbnail,unsigned gfx_thumbnail_upscale_threshold)348 void gfx_thumbnail_request_file(
349       const char *file_path, gfx_thumbnail_t *thumbnail,
350       unsigned gfx_thumbnail_upscale_threshold
351       )
352 {
353    gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr();
354    gfx_thumbnail_tag_t *thumbnail_tag = NULL;
355 
356    if (!thumbnail)
357       return;
358 
359    /* Reset thumbnail, then set 'missing' status by default
360     * (saves a number of checks later) */
361    gfx_thumbnail_reset(thumbnail);
362    thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
363 
364    /* Check if file path is valid */
365    if (string_is_empty(file_path))
366       return;
367 
368    if (!path_is_valid(file_path))
369       return;
370 
371    /* Load thumbnail */
372    thumbnail_tag = (gfx_thumbnail_tag_t*)malloc(sizeof(gfx_thumbnail_tag_t));
373 
374    if (!thumbnail_tag)
375       return;
376 
377    /* Configure user data */
378    thumbnail_tag->thumbnail = thumbnail;
379    thumbnail_tag->list_id   = p_gfx_thumb->list_id;
380 
381    /* Would like to cancel any existing image load tasks
382     * here, but can't see how to do it... */
383    if (task_push_image_load(
384          file_path, video_driver_supports_rgba(),
385          gfx_thumbnail_upscale_threshold,
386          gfx_thumbnail_handle_upload, thumbnail_tag))
387       thumbnail->status = GFX_THUMBNAIL_STATUS_PENDING;
388 }
389 
390 /* Resets (and free()s the current texture of) the
391  * specified thumbnail */
gfx_thumbnail_reset(gfx_thumbnail_t * thumbnail)392 void gfx_thumbnail_reset(gfx_thumbnail_t *thumbnail)
393 {
394    if (!thumbnail)
395       return;
396 
397    /* Unload texture */
398    if (thumbnail->texture)
399       video_driver_texture_unload(&thumbnail->texture);
400 
401    /* Ensure any 'fade in' animation is killed */
402    if (thumbnail->fade_active)
403    {
404       uintptr_t tag = (uintptr_t)&thumbnail->alpha;
405       gfx_animation_kill_by_tag(&tag);
406    }
407 
408    /* Reset all parameters */
409    thumbnail->status      = GFX_THUMBNAIL_STATUS_UNKNOWN;
410    thumbnail->texture     = 0;
411    thumbnail->width       = 0;
412    thumbnail->height      = 0;
413    thumbnail->alpha       = 0.0f;
414    thumbnail->delay_timer = 0.0f;
415    thumbnail->fade_active = false;
416 }
417 
418 /* Stream processing */
419 
420 /* Handles streaming of the specified thumbnail as it moves
421  * on/off screen
422  * - Must be called each frame for every on-screen entry
423  * - Must be called once for each entry as it moves off-screen
424  *   (or can be called each frame - overheads are small)
425  * NOTE 1: Must be called *after* gfx_thumbnail_set_system()
426  * NOTE 2: This function calls gfx_thumbnail_set_content*()
427  * NOTE 3: This function is intended for use in situations
428  *         where each menu entry has a *single* thumbnail.
429  *         If each entry has two thumbnails, use
430  *         gfx_thumbnail_process_streams() for improved
431  *         performance */
gfx_thumbnail_process_stream(gfx_thumbnail_path_data_t * path_data,gfx_animation_t * p_anim,enum gfx_thumbnail_id thumbnail_id,playlist_t * playlist,size_t idx,gfx_thumbnail_t * thumbnail,bool on_screen,unsigned gfx_thumbnail_upscale_threshold,bool network_on_demand_thumbnails)432 void gfx_thumbnail_process_stream(
433       gfx_thumbnail_path_data_t *path_data,
434       gfx_animation_t *p_anim,
435       enum gfx_thumbnail_id thumbnail_id,
436       playlist_t *playlist,
437       size_t idx,
438       gfx_thumbnail_t *thumbnail,
439       bool on_screen,
440       unsigned gfx_thumbnail_upscale_threshold,
441       bool network_on_demand_thumbnails
442       )
443 {
444    if (!thumbnail)
445       return;
446 
447    if (on_screen)
448    {
449       /* Entry is on-screen
450        * > Only process if current status is
451        *   GFX_THUMBNAIL_STATUS_UNKNOWN */
452       if (thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN)
453       {
454          gfx_thumbnail_state_t *p_gfx_thumb  = gfx_thumb_get_ptr();
455 
456          /* Check if stream delay timer has elapsed */
457          thumbnail->delay_timer             += p_anim->delta_time;
458 
459          if (thumbnail->delay_timer > p_gfx_thumb->stream_delay)
460          {
461             /* Update thumbnail content */
462             if (!path_data ||
463                 !playlist ||
464                 !gfx_thumbnail_set_content_playlist(path_data, playlist, idx))
465             {
466                /* Content is invalid
467                 * > Reset thumbnail and set missing status */
468                gfx_thumbnail_reset(thumbnail);
469                thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
470                thumbnail->alpha  = 1.0f;
471                return;
472             }
473 
474             /* Request image load */
475             gfx_thumbnail_request(
476                   path_data, thumbnail_id, playlist, idx, thumbnail,
477                   gfx_thumbnail_upscale_threshold,
478                   network_on_demand_thumbnails
479                   );
480          }
481       }
482    }
483    else
484    {
485       /* Entry is off-screen
486        * > If status is GFX_THUMBNAIL_STATUS_UNKNOWN,
487        *   thumbnail is already in a blank state - but we
488        *   must ensure that delay timer is set to zero */
489       if (thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN)
490          thumbnail->delay_timer = 0.0f;
491       /* In all other cases, reset thumbnail */
492       else
493          gfx_thumbnail_reset(thumbnail);
494    }
495 }
496 
497 /* Handles streaming of the specified thumbnails as they move
498  * on/off screen
499  * - Must be called each frame for every on-screen entry
500  * - Must be called once for each entry as it moves off-screen
501  *   (or can be called each frame - overheads are small)
502  * NOTE 1: Must be called *after* gfx_thumbnail_set_system()
503  * NOTE 2: This function calls gfx_thumbnail_set_content*()
504  * NOTE 3: This function is intended for use in situations
505  *         where each menu entry has *two* thumbnails.
506  *         If each entry only has a single thumbnail, use
507  *         gfx_thumbnail_process_stream() for improved
508  *         performance */
gfx_thumbnail_process_streams(gfx_thumbnail_path_data_t * path_data,gfx_animation_t * p_anim,playlist_t * playlist,size_t idx,gfx_thumbnail_t * right_thumbnail,gfx_thumbnail_t * left_thumbnail,bool on_screen,unsigned gfx_thumbnail_upscale_threshold,bool network_on_demand_thumbnails)509 void gfx_thumbnail_process_streams(
510       gfx_thumbnail_path_data_t *path_data,
511       gfx_animation_t *p_anim,
512       playlist_t *playlist, size_t idx,
513       gfx_thumbnail_t *right_thumbnail,
514       gfx_thumbnail_t *left_thumbnail,
515       bool on_screen,
516       unsigned gfx_thumbnail_upscale_threshold,
517       bool network_on_demand_thumbnails
518       )
519 {
520    if (!right_thumbnail || !left_thumbnail)
521       return;
522 
523    if (on_screen)
524    {
525       /* Entry is on-screen
526        * > Only process if current status is
527        *   GFX_THUMBNAIL_STATUS_UNKNOWN */
528       bool process_right = (right_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN);
529       bool process_left  = (left_thumbnail->status  == GFX_THUMBNAIL_STATUS_UNKNOWN);
530 
531       if (process_right || process_left)
532       {
533          /* Check if stream delay timer has elapsed */
534          gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr();
535          float delta_time                   = p_anim->delta_time;
536          bool request_right                 = false;
537          bool request_left                  = false;
538 
539          if (process_right)
540          {
541             right_thumbnail->delay_timer += delta_time;
542             request_right                 =
543                   (right_thumbnail->delay_timer > p_gfx_thumb->stream_delay);
544          }
545 
546          if (process_left)
547          {
548             left_thumbnail->delay_timer  += delta_time;
549             request_left                  =
550                   (left_thumbnail->delay_timer > p_gfx_thumb->stream_delay);
551          }
552 
553          /* Check if one or more thumbnails should be requested */
554          if (request_right || request_left)
555          {
556             /* Update thumbnail content */
557             if (!path_data ||
558                 !playlist ||
559                 !gfx_thumbnail_set_content_playlist(path_data, playlist, idx))
560             {
561                /* Content is invalid
562                 * > Reset thumbnail and set missing status */
563                if (request_right)
564                {
565                   gfx_thumbnail_reset(right_thumbnail);
566                   right_thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
567                   right_thumbnail->alpha  = 1.0f;
568                }
569 
570                if (request_left)
571                {
572                   gfx_thumbnail_reset(left_thumbnail);
573                   left_thumbnail->status  = GFX_THUMBNAIL_STATUS_MISSING;
574                   left_thumbnail->alpha   = 1.0f;
575                }
576 
577                return;
578             }
579 
580             /* Request image load */
581             if (request_right)
582                gfx_thumbnail_request(
583                      path_data, GFX_THUMBNAIL_RIGHT, playlist, idx, right_thumbnail,
584                      gfx_thumbnail_upscale_threshold,
585                      network_on_demand_thumbnails);
586 
587             if (request_left)
588                gfx_thumbnail_request(
589                      path_data, GFX_THUMBNAIL_LEFT, playlist, idx, left_thumbnail,
590                      gfx_thumbnail_upscale_threshold,
591                      network_on_demand_thumbnails);
592          }
593       }
594    }
595    else
596    {
597       /* Entry is off-screen
598        * > If status is GFX_THUMBNAIL_STATUS_UNKNOWN,
599        *   thumbnail is already in a blank state - but we
600        *   must ensure that delay timer is set to zero
601        * > In all other cases, reset thumbnail */
602       if (right_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN)
603          right_thumbnail->delay_timer = 0.0f;
604       else
605          gfx_thumbnail_reset(right_thumbnail);
606 
607       if (left_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN)
608          left_thumbnail->delay_timer = 0.0f;
609       else
610          gfx_thumbnail_reset(left_thumbnail);
611    }
612 }
613 
614 /* Thumbnail rendering */
615 
616 /* Determines the actual screen dimensions of a
617  * thumbnail when centred with aspect correct
618  * scaling within a rectangle of (width x height) */
gfx_thumbnail_get_draw_dimensions(gfx_thumbnail_t * thumbnail,unsigned width,unsigned height,float scale_factor,float * draw_width,float * draw_height)619 void gfx_thumbnail_get_draw_dimensions(
620       gfx_thumbnail_t *thumbnail,
621       unsigned width, unsigned height, float scale_factor,
622       float *draw_width, float *draw_height)
623 {
624    float display_aspect;
625    float thumbnail_aspect;
626 
627    /* Sanity check */
628    if (!thumbnail || (width < 1) || (height < 1))
629       goto error;
630 
631    if ((thumbnail->width < 1) || (thumbnail->height < 1))
632       goto error;
633 
634    /* Account for display/thumbnail aspect ratio
635     * differences */
636    display_aspect   = (float)width            / (float)height;
637    thumbnail_aspect = (float)thumbnail->width / (float)thumbnail->height;
638 
639    if (thumbnail_aspect > display_aspect)
640    {
641       *draw_width  = (float)width;
642       *draw_height = (float)thumbnail->height * (*draw_width / (float)thumbnail->width);
643    }
644    else
645    {
646       *draw_height = (float)height;
647       *draw_width  = (float)thumbnail->width * (*draw_height / (float)thumbnail->height);
648    }
649 
650    /* Account for scale factor
651     * > Side note: We cannot use the gfx_display_ctx_draw_t
652     *   'scale_factor' parameter for scaling thumbnails,
653     *   since this clips off any part of the expanded image
654     *   that extends beyond the bounding box. But even if
655     *   it didn't, we can't get real screen dimensions
656     *   without scaling manually... */
657    *draw_width  *= scale_factor;
658    *draw_height *= scale_factor;
659    return;
660 
661 error:
662    *draw_width  = 0.0f;
663    *draw_height = 0.0f;
664 }
665 
666 /* Draws specified thumbnail with specified alignment
667  * (and aspect correct scaling) within a rectangle of
668  * (width x height).
669  * 'shadow' defines an optional shadow effect (may be
670  * set to NULL if a shadow effect is not required).
671  * NOTE: Setting scale_factor > 1.0f will increase the
672  *       size of the thumbnail beyond the limits of the
673  *       (width x height) rectangle (alignment + aspect
674  *       correct scaling is preserved). Use with caution */
675 
gfx_thumbnail_draw(void * userdata,unsigned video_width,unsigned video_height,gfx_thumbnail_t * thumbnail,float x,float y,unsigned width,unsigned height,enum gfx_thumbnail_alignment alignment,float alpha,float scale_factor,gfx_thumbnail_shadow_t * shadow)676 void gfx_thumbnail_draw(
677       void *userdata,
678       unsigned video_width,
679       unsigned video_height,
680       gfx_thumbnail_t *thumbnail,
681       float x, float y, unsigned width, unsigned height,
682       enum gfx_thumbnail_alignment alignment,
683       float alpha, float scale_factor,
684       gfx_thumbnail_shadow_t *shadow)
685 {
686    gfx_display_t            *p_disp  = disp_get_ptr();
687    gfx_display_ctx_driver_t *dispctx = p_disp->dispctx;
688    /* Sanity check */
689    if (!thumbnail ||
690        (width < 1) || (height < 1) || (alpha <= 0.0f) || (scale_factor <= 0.0f))
691       return;
692    if (!dispctx)
693       return;
694 
695    /* Only draw thumbnail if it is available... */
696    if (thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE)
697    {
698       gfx_display_ctx_rotate_draw_t rotate_draw;
699       gfx_display_ctx_draw_t draw;
700       struct video_coords coords;
701       math_matrix_4x4 mymat;
702       float draw_width;
703       float draw_height;
704       float draw_x;
705       float draw_y;
706       float thumbnail_alpha     = thumbnail->alpha * alpha;
707       float thumbnail_color[16] = {
708          1.0f, 1.0f, 1.0f, 1.0f,
709          1.0f, 1.0f, 1.0f, 1.0f,
710          1.0f, 1.0f, 1.0f, 1.0f,
711          1.0f, 1.0f, 1.0f, 1.0f
712       };
713 
714       /* Set thumbnail opacity */
715       if (thumbnail_alpha <= 0.0f)
716          return;
717       if (thumbnail_alpha < 1.0f)
718          gfx_display_set_alpha(thumbnail_color, thumbnail_alpha);
719 
720       /* Get thumbnail dimensions */
721       gfx_thumbnail_get_draw_dimensions(
722             thumbnail, width, height, scale_factor,
723             &draw_width, &draw_height);
724 
725       if (dispctx->blend_begin)
726          dispctx->blend_begin(userdata);
727 
728       /* Perform 'rotation' step
729        * > Note that rotation does not actually work...
730        * > It rotates the image all right, but distorts it
731        *   to fit the aspect of the bounding box while clipping
732        *   off any 'corners' that extend beyond the bounding box
733        * > Since the result is visual garbage, we disable
734        *   rotation entirely
735        * > But we still have to call gfx_display_rotate_z(),
736        *   or nothing will be drawn...
737        * Note that we also disable scaling here (scale_enable),
738        * since we handle scaling internally... */
739       rotate_draw.matrix       = &mymat;
740       rotate_draw.rotation     = 0.0f;
741       rotate_draw.scale_x      = 1.0f;
742       rotate_draw.scale_y      = 1.0f;
743       rotate_draw.scale_z      = 1.0f;
744       rotate_draw.scale_enable = false;
745 
746       gfx_display_rotate_z(p_disp, &rotate_draw, userdata);
747 
748       /* Configure draw object
749        * > Note: Colour, width/height and position must
750        *   be set *after* drawing any shadow effects */
751       coords.vertices      = 4;
752       coords.vertex        = NULL;
753       coords.tex_coord     = NULL;
754       coords.lut_tex_coord = NULL;
755 
756       draw.scale_factor    = 1.0f;
757       draw.rotation        = 0.0f;
758       draw.coords          = &coords;
759       draw.matrix_data     = &mymat;
760       draw.texture         = thumbnail->texture;
761       draw.prim_type       = GFX_DISPLAY_PRIM_TRIANGLESTRIP;
762       draw.pipeline_id     = 0;
763 
764       /* Set thumbnail alignment within bounding box */
765       switch (alignment)
766       {
767          case GFX_THUMBNAIL_ALIGN_TOP:
768             /* Centred horizontally */
769             draw_x = x + ((float)width - draw_width) / 2.0f;
770             /* Drawn at top of bounding box */
771             draw_y = (float)video_height - y - draw_height;
772             break;
773          case GFX_THUMBNAIL_ALIGN_BOTTOM:
774             /* Centred horizontally */
775             draw_x = x + ((float)width - draw_width) / 2.0f;
776             /* Drawn at bottom of bounding box */
777             draw_y = (float)video_height - y - (float)height;
778             break;
779          case GFX_THUMBNAIL_ALIGN_LEFT:
780             /* Drawn at left side of bounding box */
781             draw_x = x;
782             /* Centred vertically */
783             draw_y = (float)video_height - y - draw_height - ((float)height - draw_height) / 2.0f;
784             break;
785          case GFX_THUMBNAIL_ALIGN_RIGHT:
786             /* Drawn at right side of bounding box */
787             draw_x = x + (float)width - draw_width;
788             /* Centred vertically */
789             draw_y = (float)video_height - y - draw_height - ((float)height - draw_height) / 2.0f;
790             break;
791          case GFX_THUMBNAIL_ALIGN_CENTRE:
792          default:
793             /* Centred both horizontally and vertically */
794             draw_x = x + ((float)width - draw_width) / 2.0f;
795             draw_y = (float)video_height - y - draw_height - ((float)height - draw_height) / 2.0f;
796             break;
797       }
798 
799       /* Draw shadow effect, if required */
800       if (shadow)
801       {
802          /* Sanity check */
803          if ((shadow->type != GFX_THUMBNAIL_SHADOW_NONE) &&
804                (shadow->alpha > 0.0f))
805          {
806             float shadow_width;
807             float shadow_height;
808             float shadow_x;
809             float shadow_y;
810             float shadow_color[16] = {
811                0.0f, 0.0f, 0.0f, 1.0f,
812                0.0f, 0.0f, 0.0f, 1.0f,
813                0.0f, 0.0f, 0.0f, 1.0f,
814                0.0f, 0.0f, 0.0f, 1.0f
815             };
816             float shadow_alpha     = thumbnail_alpha;
817 
818             /* Set shadow opacity */
819             if (shadow->alpha < 1.0f)
820                shadow_alpha *= shadow->alpha;
821 
822             gfx_display_set_alpha(shadow_color, shadow_alpha);
823 
824             /* Configure shadow based on effect type
825              * > Not using a switch() here, since we've
826              *   already eliminated GFX_THUMBNAIL_SHADOW_NONE */
827             if (shadow->type == GFX_THUMBNAIL_SHADOW_OUTLINE)
828             {
829                shadow_width  = draw_width  + (float)(shadow->outline.width * 2);
830                shadow_height = draw_height + (float)(shadow->outline.width * 2);
831                shadow_x      = draw_x - (float)shadow->outline.width;
832                shadow_y      = draw_y - (float)shadow->outline.width;
833             }
834             /* Default: GFX_THUMBNAIL_SHADOW_DROP */
835             else
836             {
837                shadow_width  = draw_width;
838                shadow_height = draw_height;
839                shadow_x      = draw_x + shadow->drop.x_offset;
840                shadow_y      = draw_y - shadow->drop.y_offset;
841             }
842 
843             /* Apply shadow draw object configuration */
844             coords.color = (const float*)shadow_color;
845             draw.width   = (unsigned)shadow_width;
846             draw.height  = (unsigned)shadow_height;
847             draw.x       = shadow_x;
848             draw.y       = shadow_y;
849 
850             /* Draw shadow */
851             if (draw.height > 0 && draw.width > 0)
852                if (dispctx->draw)
853                   dispctx->draw(&draw, userdata, video_width, video_height);
854          }
855       }
856 
857       /* Final thumbnail draw object configuration */
858       coords.color = (const float*)thumbnail_color;
859       draw.width   = (unsigned)draw_width;
860       draw.height  = (unsigned)draw_height;
861       draw.x       = draw_x;
862       draw.y       = draw_y;
863 
864       /* Draw thumbnail */
865       if (draw.height > 0 && draw.width > 0)
866          if (dispctx->draw)
867             dispctx->draw(&draw, userdata, video_width, video_height);
868 
869       if (dispctx->blend_end)
870          dispctx->blend_end(userdata);
871    }
872 }
873