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