RetroArch/gfx/gfx_thumbnail.c

1069 lines
37 KiB
C

/* Copyright (C) 2010-2019 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (gfx_thumbnail.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <features/features_cpu.h>
#include <file/file_path.h>
#include <string/stdstring.h>
#include "gfx_display.h"
#include "gfx_animation.h"
#include "gfx_thumbnail.h"
#include "../tasks/tasks_internal.h"
#define DEFAULT_GFX_THUMBNAIL_STREAM_DELAY 83.333333f
#define DEFAULT_GFX_THUMBNAIL_FADE_DURATION 166.66667f
/* Utility structure, sent as userdata when pushing
* an image load */
typedef struct
{
uint64_t list_id;
gfx_thumbnail_t *thumbnail;
} gfx_thumbnail_tag_t;
static gfx_thumbnail_state_t gfx_thumb_st = {0}; /* uint64_t alignment */
gfx_thumbnail_state_t *gfx_thumb_get_ptr(void)
{
return &gfx_thumb_st;
}
/* Setters */
/* When streaming thumbnails, sets time in ms that an
* entry must be on screen before an image load is
* requested
* > if 'delay' is negative, default value is set */
void gfx_thumbnail_set_stream_delay(float delay)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
p_gfx_thumb->stream_delay = (delay >= 0.0f) ?
delay : DEFAULT_GFX_THUMBNAIL_STREAM_DELAY;
}
/* Sets duration in ms of the thumbnail 'fade in'
* animation
* > If 'duration' is negative, default value is set */
void gfx_thumbnail_set_fade_duration(float duration)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
p_gfx_thumb->fade_duration = (duration >= 0.0f) ?
duration : DEFAULT_GFX_THUMBNAIL_FADE_DURATION;
}
/* Specifies whether 'fade in' animation should be
* triggered for missing thumbnails
* > When 'true', allows menu driver to animate
* any 'thumbnail unavailable' notifications */
void gfx_thumbnail_set_fade_missing(bool fade_missing)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
p_gfx_thumb->fade_missing = fade_missing;
}
/* Callbacks */
/* Fade animation callback - simply resets thumbnail
* 'fade_active' status */
static void gfx_thumbnail_fade_cb(void *userdata)
{
gfx_thumbnail_t *thumbnail = (gfx_thumbnail_t*)userdata;
if (thumbnail)
thumbnail->flags |= GFX_THUMB_FLAG_FADE_ACTIVE;
}
/* Initialises thumbnail 'fade in' animation */
static void gfx_thumbnail_init_fade(
gfx_thumbnail_state_t *p_gfx_thumb,
gfx_thumbnail_t *thumbnail)
{
/* Sanity check */
if (!thumbnail)
return;
/* A 'fade in' animation is triggered if:
* - Thumbnail is available
* - Thumbnail is missing and 'fade_missing' is enabled */
if ( (thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE)
|| (p_gfx_thumb->fade_missing
&& (thumbnail->status == GFX_THUMBNAIL_STATUS_MISSING)))
{
if (p_gfx_thumb->fade_duration > 0.0f)
{
gfx_animation_ctx_entry_t animation_entry;
thumbnail->alpha = 0.0f;
thumbnail->flags |= GFX_THUMB_FLAG_FADE_ACTIVE;
animation_entry.easing_enum = EASING_OUT_QUAD;
animation_entry.tag = (uintptr_t)&thumbnail->alpha;
animation_entry.duration = p_gfx_thumb->fade_duration;
animation_entry.target_value = 1.0f;
animation_entry.subject = &thumbnail->alpha;
animation_entry.cb = gfx_thumbnail_fade_cb;
animation_entry.userdata = thumbnail;
gfx_animation_push(&animation_entry);
}
else
thumbnail->alpha = 1.0f;
}
}
/* Used to process thumbnail data following completion
* of image load task */
static void gfx_thumbnail_handle_upload(
retro_task_t *task, void *task_data, void *user_data, const char *err)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
struct texture_image *img = (struct texture_image*)task_data;
gfx_thumbnail_tag_t *thumbnail_tag = (gfx_thumbnail_tag_t*)user_data;
bool fade_enabled = false;
/* Sanity check */
if (!thumbnail_tag)
goto end;
/* Ensure that we are operating on the correct
* thumbnail... */
if (thumbnail_tag->list_id != p_gfx_thumb->list_id)
goto end;
/* Only process image if we are waiting for it */
if (thumbnail_tag->thumbnail->status != GFX_THUMBNAIL_STATUS_PENDING)
goto end;
/* Sanity check: if thumbnail already has a texture,
* we're in some kind of weird error state - in this
* case, the best course of action is to just reset
* the thumbnail... */
if (thumbnail_tag->thumbnail->texture)
gfx_thumbnail_reset(thumbnail_tag->thumbnail);
/* Set thumbnail 'missing' status by default
* (saves a number of checks later) */
thumbnail_tag->thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
/* If we reach this stage, thumbnail 'fade in'
* animations should be applied (based on current
* thumbnail status and global configuration) */
fade_enabled = true;
/* Check we have a valid image */
if (!img || (img->width < 1) || (img->height < 1))
goto end;
/* Upload texture to GPU */
if (!video_driver_texture_load(
img, TEXTURE_FILTER_MIPMAP_LINEAR,
&thumbnail_tag->thumbnail->texture))
goto end;
/* Cache dimensions */
thumbnail_tag->thumbnail->width = img->width;
thumbnail_tag->thumbnail->height = img->height;
/* Update thumbnail status */
thumbnail_tag->thumbnail->status = GFX_THUMBNAIL_STATUS_AVAILABLE;
end:
/* Clean up */
if (img)
{
image_texture_free(img);
free(img);
}
if (thumbnail_tag)
{
/* Trigger 'fade in' animation, if required */
if (fade_enabled)
gfx_thumbnail_init_fade(p_gfx_thumb,
thumbnail_tag->thumbnail);
free(thumbnail_tag);
}
}
/* Core interface */
/* When called, prevents the handling of any pending
* thumbnail load requests
* >> **MUST** be called before deleting any gfx_thumbnail_t
* objects passed to gfx_thumbnail_request() or
* gfx_thumbnail_process_stream(), otherwise
* heap-use-after-free errors *will* occur */
void gfx_thumbnail_cancel_pending_requests(void)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
p_gfx_thumb->list_id++;
}
/* Requests loading of the specified thumbnail
* - If operation fails, 'thumbnail->status' will be set to
* GFX_THUMBNAIL_STATUS_MISSING
* - If operation is successful, 'thumbnail->status' will be
* set to GFX_THUMBNAIL_STATUS_PENDING
* 'thumbnail' will be populated with texture info/metadata
* once the image load is complete
* NOTE 1: Must be called *after* gfx_thumbnail_set_system()
* and gfx_thumbnail_set_content*()
* NOTE 2: 'playlist' and 'idx' are only required here for
* on-demand thumbnail download support
* (an annoyance...) */
void 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)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
if (!path_data || !thumbnail)
return;
/* Reset thumbnail, then set 'missing' status by default
* (saves a number of checks later) */
gfx_thumbnail_reset(thumbnail);
thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
/* Update/extract thumbnail path */
if (gfx_thumbnail_is_enabled(path_data, thumbnail_id))
{
if (gfx_thumbnail_update_path(path_data, thumbnail_id))
{
const char *thumbnail_path = NULL;
if (gfx_thumbnail_get_path(path_data, thumbnail_id, &thumbnail_path))
{
/* Load thumbnail, if required */
if (path_is_valid(thumbnail_path))
{
gfx_thumbnail_tag_t *thumbnail_tag =
(gfx_thumbnail_tag_t*)malloc(sizeof(gfx_thumbnail_tag_t));
if (!thumbnail_tag)
goto end;
/* Configure user data */
thumbnail_tag->thumbnail = thumbnail;
thumbnail_tag->list_id = p_gfx_thumb->list_id;
/* Would like to cancel any existing image load tasks
* here, but can't see how to do it... */
if (task_push_image_load(
thumbnail_path, video_driver_supports_rgba(),
gfx_thumbnail_upscale_threshold,
gfx_thumbnail_handle_upload, thumbnail_tag))
thumbnail->status = GFX_THUMBNAIL_STATUS_PENDING;
}
#ifdef HAVE_NETWORKING
/* Handle on demand thumbnail downloads */
else if (network_on_demand_thumbnails)
{
const char *system = NULL;
const char *img_name = NULL;
static char last_img_name[PATH_MAX_LENGTH] = {0};
settings_t *settings = config_get_ptr();
enum playlist_thumbnail_name_flags curr_flag;
if (!playlist)
goto end;
/* Validate entry */
if (!gfx_thumbnail_get_img_name(path_data, &img_name, PLAYLIST_THUMBNAIL_FLAG_STD_NAME))
goto end;
/* Only trigger a thumbnail download if image
* name has changed since the last call of
* gfx_thumbnail_request()
* > Allows gfx_thumbnail_request() to be used
* for successive right/left thumbnail requests
* with minimal duplication of effort
* (i.e. task_push_pl_entry_thumbnail_download()
* will automatically cancel if a download for the
* existing playlist entry is pending, but the
* checks required for this involve significant
* overheads. We can avoid this entirely with
* a simple string comparison) */
if (string_is_equal(img_name, last_img_name))
goto end;
strlcpy(last_img_name, img_name, sizeof(last_img_name));
/* Get system name */
if (!gfx_thumbnail_get_system(path_data, &system))
goto end;
/* Since task_push_pl_entry_download will shift the flag, do not attempt if it is already
* at second to last option. */
curr_flag = playlist_get_curr_thumbnail_name_flag(playlist,idx);
if (curr_flag & PLAYLIST_THUMBNAIL_FLAG_NONE || curr_flag & PLAYLIST_THUMBNAIL_FLAG_SHORT_NAME)
goto end;
/* Do not try to fetch full names here, if it is not explicitly wanted */
if (!settings->bools.playlist_use_filename &&
!playlist_thumbnail_match_with_filename(playlist) &&
curr_flag == PLAYLIST_THUMBNAIL_FLAG_INVALID)
playlist_update_thumbnail_name_flag(playlist, idx, PLAYLIST_THUMBNAIL_FLAG_FULL_NAME);
/* Trigger thumbnail download *
* Note: download will grab all 3 possible thumbnails, no matter
* what left/right thumbnails are set at the moment */
task_push_pl_entry_thumbnail_download(
system, playlist, (unsigned)idx,
false, true);
}
#endif
}
}
}
end:
/* Trigger 'fade in' animation, if required */
if (thumbnail->status != GFX_THUMBNAIL_STATUS_PENDING)
gfx_thumbnail_init_fade(p_gfx_thumb,
thumbnail);
}
/* Requests loading of a specific thumbnail image file
* (may be used, for example, to load savestate images)
* - If operation fails, 'thumbnail->status' will be set to
* MUI_THUMBNAIL_STATUS_MISSING
* - If operation is successful, 'thumbnail->status' will be
* set to MUI_THUMBNAIL_STATUS_PENDING
* 'thumbnail' will be populated with texture info/metadata
* once the image load is complete */
void gfx_thumbnail_request_file(
const char *file_path, gfx_thumbnail_t *thumbnail,
unsigned gfx_thumbnail_upscale_threshold)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
gfx_thumbnail_tag_t *thumbnail_tag = NULL;
if (!thumbnail)
return;
/* Reset thumbnail, then set 'missing' status by default
* (saves a number of checks later) */
gfx_thumbnail_reset(thumbnail);
thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
/* Check if file path is valid */
if ( string_is_empty(file_path)
|| !path_is_valid(file_path))
return;
/* Load thumbnail */
if (!(thumbnail_tag = (gfx_thumbnail_tag_t*)malloc(sizeof(gfx_thumbnail_tag_t))))
return;
/* Configure user data */
thumbnail_tag->thumbnail = thumbnail;
thumbnail_tag->list_id = p_gfx_thumb->list_id;
/* Would like to cancel any existing image load tasks
* here, but can't see how to do it... */
if (task_push_image_load(
file_path, video_driver_supports_rgba(),
gfx_thumbnail_upscale_threshold,
gfx_thumbnail_handle_upload, thumbnail_tag))
thumbnail->status = GFX_THUMBNAIL_STATUS_PENDING;
}
/* Resets (and free()s the current texture of) the
* specified thumbnail */
void gfx_thumbnail_reset(gfx_thumbnail_t *thumbnail)
{
if (!thumbnail)
return;
/* Unload texture */
if (thumbnail->texture)
video_driver_texture_unload(&thumbnail->texture);
/* Ensure any 'fade in' animation is killed */
if (thumbnail->flags & GFX_THUMB_FLAG_FADE_ACTIVE)
{
uintptr_t tag = (uintptr_t)&thumbnail->alpha;
gfx_animation_kill_by_tag(&tag);
}
/* Reset all parameters */
thumbnail->status = GFX_THUMBNAIL_STATUS_UNKNOWN;
thumbnail->texture = 0;
thumbnail->width = 0;
thumbnail->height = 0;
thumbnail->alpha = 0.0f;
thumbnail->delay_timer = 0.0f;
thumbnail->flags &= ~(GFX_THUMB_FLAG_FADE_ACTIVE
| GFX_THUMB_FLAG_CORE_ASPECT);
}
/* Stream processing */
/* Requests loading of the specified thumbnail via
* the stream interface
* - Must be called on each frame for the duration
* that specified thumbnail is on-screen
* - Actual load request is deferred by currently
* set stream delay
* - Function becomes a no-op once load request is
* made
* - Thumbnails loaded via this function must be
* deleted manually via gfx_thumbnail_reset()
* when they move off-screen
* NOTE 1: Must be called *after* gfx_thumbnail_set_system()
* and gfx_thumbnail_set_content*()
* NOTE 2: 'playlist' and 'idx' are only required here for
* on-demand thumbnail download support
* (an annoyance...)
* NOTE 3: This function is intended for use in situations
* where each menu entry has a *single* thumbnail.
* If each entry has two thumbnails, use
* gfx_thumbnail_request_streams() for improved
* performance */
void gfx_thumbnail_request_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,
unsigned gfx_thumbnail_upscale_threshold,
bool network_on_demand_thumbnails)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
/* Only process request if current status
* is GFX_THUMBNAIL_STATUS_UNKNOWN */
if ( !thumbnail
|| (thumbnail->status != GFX_THUMBNAIL_STATUS_UNKNOWN))
return;
/* Check if stream delay timer has elapsed */
thumbnail->delay_timer += p_anim->delta_time;
if (thumbnail->delay_timer > p_gfx_thumb->stream_delay)
{
/* Sanity check */
if (!path_data)
{
/* No path information
* > Reset thumbnail and set missing status
* to prevent repeated load attempts */
gfx_thumbnail_reset(thumbnail);
thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
thumbnail->alpha = 1.0f;
return;
}
/* Request image load */
gfx_thumbnail_request(
path_data, thumbnail_id, playlist, idx, thumbnail,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
}
}
/* Requests loading of the specified thumbnails via
* the stream interface
* - Must be called on each frame for the duration
* that specified thumbnails are on-screen
* - Actual load request is deferred by currently
* set stream delay
* - Function becomes a no-op once load request is
* made
* - Thumbnails loaded via this function must be
* deleted manually via gfx_thumbnail_reset()
* when they move off-screen
* NOTE 1: Must be called *after* gfx_thumbnail_set_system()
* and gfx_thumbnail_set_content*()
* NOTE 2: 'playlist' and 'idx' are only required here for
* on-demand thumbnail download support
* (an annoyance...)
* NOTE 3: This function is intended for use in situations
* where each menu entry has *two* thumbnails.
* If each entry only has a single thumbnail, use
* gfx_thumbnail_request_stream() for improved
* performance */
void gfx_thumbnail_request_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,
unsigned gfx_thumbnail_upscale_threshold,
bool network_on_demand_thumbnails)
{
bool process_right = false;
bool process_left = false;
if (!right_thumbnail || !left_thumbnail)
return;
/* Only process request if current status
* is GFX_THUMBNAIL_STATUS_UNKNOWN */
process_right = (right_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN);
process_left = (left_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN);
if (process_right || process_left)
{
/* Check if stream delay timer has elapsed */
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
float delta_time = p_anim->delta_time;
bool request_right = false;
bool request_left = false;
if (process_right)
{
right_thumbnail->delay_timer += delta_time;
request_right =
(right_thumbnail->delay_timer > p_gfx_thumb->stream_delay);
}
if (process_left)
{
left_thumbnail->delay_timer += delta_time;
request_left =
(left_thumbnail->delay_timer > p_gfx_thumb->stream_delay);
}
/* Check if one or more thumbnails should be requested */
if (request_right || request_left)
{
/* Sanity check */
if (!path_data)
{
/* No path information
* > Reset thumbnail and set missing status
* to prevent repeated load attempts */
if (request_right)
{
gfx_thumbnail_reset(right_thumbnail);
right_thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
right_thumbnail->alpha = 1.0f;
}
if (request_left)
{
gfx_thumbnail_reset(left_thumbnail);
left_thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
left_thumbnail->alpha = 1.0f;
}
return;
}
/* Request image load */
if (request_right)
gfx_thumbnail_request(
path_data, GFX_THUMBNAIL_RIGHT, playlist, idx, right_thumbnail,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
if (request_left)
gfx_thumbnail_request(
path_data, GFX_THUMBNAIL_LEFT, playlist, idx, left_thumbnail,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
}
}
}
/* Handles streaming of the specified thumbnail as it moves
* on/off screen
* - Must be called each frame for every on-screen entry
* - Must be called once for each entry as it moves off-screen
* (or can be called each frame - overheads are small)
* NOTE 1: Must be called *after* gfx_thumbnail_set_system()
* NOTE 2: This function calls gfx_thumbnail_set_content*()
* NOTE 3: This function is intended for use in situations
* where each menu entry has a *single* thumbnail.
* If each entry has two thumbnails, use
* gfx_thumbnail_process_streams() for improved
* performance */
void 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)
{
if (!thumbnail)
return;
if (on_screen)
{
/* Entry is on-screen
* > Only process if current status is
* GFX_THUMBNAIL_STATUS_UNKNOWN */
if (thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
/* Check if stream delay timer has elapsed */
thumbnail->delay_timer += p_anim->delta_time;
if (thumbnail->delay_timer > p_gfx_thumb->stream_delay)
{
/* Update thumbnail content */
if ( !path_data
|| !playlist
|| !gfx_thumbnail_set_content_playlist(path_data, playlist, idx))
{
/* Content is invalid
* > Reset thumbnail and set missing status */
gfx_thumbnail_reset(thumbnail);
thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
thumbnail->alpha = 1.0f;
return;
}
/* Request image load */
gfx_thumbnail_request(
path_data, thumbnail_id, playlist, idx, thumbnail,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
}
}
}
else
{
/* Entry is off-screen
* > If status is GFX_THUMBNAIL_STATUS_UNKNOWN,
* thumbnail is already in a blank state - but we
* must ensure that delay timer is set to zero */
if (thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN)
thumbnail->delay_timer = 0.0f;
/* In all other cases, reset thumbnail */
else
gfx_thumbnail_reset(thumbnail);
}
}
/* Handles streaming of the specified thumbnails as they move
* on/off screen
* - Must be called each frame for every on-screen entry
* - Must be called once for each entry as it moves off-screen
* (or can be called each frame - overheads are small)
* NOTE 1: Must be called *after* gfx_thumbnail_set_system()
* NOTE 2: This function calls gfx_thumbnail_set_content*()
* NOTE 3: This function is intended for use in situations
* where each menu entry has *two* thumbnails.
* If each entry only has a single thumbnail, use
* gfx_thumbnail_process_stream() for improved
* performance */
void 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)
{
if (!right_thumbnail || !left_thumbnail)
return;
if (on_screen)
{
/* Entry is on-screen
* > Only process if current status is
* GFX_THUMBNAIL_STATUS_UNKNOWN */
bool process_right = (right_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN);
bool process_left = (left_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN);
if (process_right || process_left)
{
/* Check if stream delay timer has elapsed */
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
float delta_time = p_anim->delta_time;
bool request_right = false;
bool request_left = false;
if (process_right)
{
right_thumbnail->delay_timer += delta_time;
request_right =
(right_thumbnail->delay_timer > p_gfx_thumb->stream_delay);
}
if (process_left)
{
left_thumbnail->delay_timer += delta_time;
request_left =
(left_thumbnail->delay_timer > p_gfx_thumb->stream_delay);
}
/* Check if one or more thumbnails should be requested */
if (request_right || request_left)
{
/* Update thumbnail content */
if ( !path_data
|| !playlist
|| !gfx_thumbnail_set_content_playlist(path_data, playlist, idx))
{
/* Content is invalid
* > Reset thumbnail and set missing status */
if (request_right)
{
gfx_thumbnail_reset(right_thumbnail);
right_thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
right_thumbnail->alpha = 1.0f;
}
if (request_left)
{
gfx_thumbnail_reset(left_thumbnail);
left_thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
left_thumbnail->alpha = 1.0f;
}
return;
}
/* Request image load */
if (request_right)
gfx_thumbnail_request(
path_data, GFX_THUMBNAIL_RIGHT, playlist, idx, right_thumbnail,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
if (request_left)
gfx_thumbnail_request(
path_data, GFX_THUMBNAIL_LEFT, playlist, idx, left_thumbnail,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
}
}
}
else
{
/* Entry is off-screen
* > If status is GFX_THUMBNAIL_STATUS_UNKNOWN,
* thumbnail is already in a blank state - but we
* must ensure that delay timer is set to zero
* > In all other cases, reset thumbnail */
if (right_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN)
right_thumbnail->delay_timer = 0.0f;
else
gfx_thumbnail_reset(right_thumbnail);
if (left_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN)
left_thumbnail->delay_timer = 0.0f;
else
gfx_thumbnail_reset(left_thumbnail);
}
}
/* Thumbnail rendering */
/* Determines the actual screen dimensions of a
* thumbnail when centred with aspect correct
* scaling within a rectangle of (width x height) */
void gfx_thumbnail_get_draw_dimensions(
gfx_thumbnail_t *thumbnail,
unsigned width, unsigned height, float scale_factor,
float *draw_width, float *draw_height)
{
float core_aspect;
float display_aspect;
float thumbnail_aspect;
video_driver_state_t *video_st = video_state_get_ptr();
/* Sanity check */
if ( !thumbnail
|| (width < 1)
|| (height < 1)
|| (thumbnail->width < 1)
|| (thumbnail->height < 1))
{
*draw_width = 0.0f;
*draw_height = 0.0f;
return;
}
/* Account for display/thumbnail/core aspect ratio
* differences */
display_aspect = (float)width / (float)height;
thumbnail_aspect = (float)thumbnail->width / (float)thumbnail->height;
core_aspect = ((thumbnail->flags & GFX_THUMB_FLAG_CORE_ASPECT)
&& video_st && video_st->av_info.geometry.aspect_ratio > 0)
? video_st->av_info.geometry.aspect_ratio
: thumbnail_aspect;
if (thumbnail_aspect > display_aspect)
{
*draw_width = (float)width;
*draw_height = (float)thumbnail->height * (*draw_width / (float)thumbnail->width);
if (thumbnail->flags & GFX_THUMB_FLAG_CORE_ASPECT)
{
*draw_height = *draw_height * (thumbnail_aspect / core_aspect);
if (*draw_height > height)
{
*draw_height = (float)height;
*draw_width = (float)thumbnail->width * (*draw_height / (float)thumbnail->height);
*draw_width = *draw_width / (thumbnail_aspect / core_aspect);
}
}
}
else
{
*draw_height = (float)height;
*draw_width = (float)thumbnail->width * (*draw_height / (float)thumbnail->height);
if (thumbnail->flags & GFX_THUMB_FLAG_CORE_ASPECT)
*draw_width = *draw_width / (thumbnail_aspect / core_aspect);
}
/* Account for scale factor
* > Side note: We cannot use the gfx_display_ctx_draw_t
* 'scale_factor' parameter for scaling thumbnails,
* since this clips off any part of the expanded image
* that extends beyond the bounding box. But even if
* it didn't, we can't get real screen dimensions
* without scaling manually... */
*draw_width *= scale_factor;
*draw_height *= scale_factor;
}
/* Draws specified thumbnail with specified alignment
* (and aspect correct scaling) within a rectangle of
* (width x height).
* 'shadow' defines an optional shadow effect (may be
* set to NULL if a shadow effect is not required).
* NOTE: Setting scale_factor > 1.0f will increase the
* size of the thumbnail beyond the limits of the
* (width x height) rectangle (alignment + aspect
* correct scaling is preserved). Use with caution */
void 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)
{
gfx_display_t *p_disp = disp_get_ptr();
gfx_display_ctx_driver_t *dispctx = p_disp->dispctx;
/* Sanity check */
if (
!thumbnail
|| !dispctx
|| (width < 1)
|| (height < 1)
|| (alpha <= 0.0f)
|| (scale_factor <= 0.0f)
)
return;
/* Only draw thumbnail if it is available... */
if (thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE)
{
gfx_display_ctx_draw_t draw;
struct video_coords coords;
math_matrix_4x4 mymat;
float draw_width;
float draw_height;
float draw_x;
float draw_y;
float thumbnail_alpha = thumbnail->alpha * alpha;
float thumbnail_color[16] = {
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
/* Set thumbnail opacity */
if (thumbnail_alpha <= 0.0f)
return;
if (thumbnail_alpha < 1.0f)
gfx_display_set_alpha(thumbnail_color, thumbnail_alpha);
/* Get thumbnail dimensions */
gfx_thumbnail_get_draw_dimensions(
thumbnail, width, height, scale_factor,
&draw_width, &draw_height);
if (dispctx->blend_begin)
dispctx->blend_begin(userdata);
if (!p_disp->dispctx->handles_transform)
{
/* Perform 'rotation' step
* > Note that rotation does not actually work...
* > It rotates the image all right, but distorts it
* to fit the aspect of the bounding box while clipping
* off any 'corners' that extend beyond the bounding box
* > Since the result is visual garbage, we disable
* rotation entirely
* > But we still have to call gfx_display_rotate_z(),
* or nothing will be drawn...
*/
float cosine = 1.0f; /* cos(rad) = cos(0) = 1.0f */
float sine = 0.0f; /* sine(rad) = sine(0) = 0.0f */
gfx_display_rotate_z(p_disp, &mymat, cosine, sine, userdata);
}
/* Configure draw object
* > Note: Colour, width/height and position must
* be set *after* drawing any shadow effects */
coords.vertices = 4;
coords.vertex = NULL;
coords.tex_coord = NULL;
coords.lut_tex_coord = NULL;
draw.scale_factor = 1.0f;
draw.rotation = 0.0f;
draw.coords = &coords;
draw.matrix_data = &mymat;
draw.texture = thumbnail->texture;
draw.prim_type = GFX_DISPLAY_PRIM_TRIANGLESTRIP;
draw.pipeline_id = 0;
/* Set thumbnail alignment within bounding box */
switch (alignment)
{
case GFX_THUMBNAIL_ALIGN_TOP:
/* Centred horizontally */
draw_x = x + ((float)width - draw_width) / 2.0f;
/* Drawn at top of bounding box */
draw_y = (float)video_height - y - draw_height;
break;
case GFX_THUMBNAIL_ALIGN_BOTTOM:
/* Centred horizontally */
draw_x = x + ((float)width - draw_width) / 2.0f;
/* Drawn at bottom of bounding box */
draw_y = (float)video_height - y - (float)height;
break;
case GFX_THUMBNAIL_ALIGN_LEFT:
/* Drawn at left side of bounding box */
draw_x = x;
/* Centred vertically */
draw_y = (float)video_height - y - draw_height - ((float)height - draw_height) / 2.0f;
break;
case GFX_THUMBNAIL_ALIGN_RIGHT:
/* Drawn at right side of bounding box */
draw_x = x + (float)width - draw_width;
/* Centred vertically */
draw_y = (float)video_height - y - draw_height - ((float)height - draw_height) / 2.0f;
break;
case GFX_THUMBNAIL_ALIGN_CENTRE:
default:
/* Centred both horizontally and vertically */
draw_x = x + ((float)width - draw_width) / 2.0f;
draw_y = (float)video_height - y - draw_height - ((float)height - draw_height) / 2.0f;
break;
}
/* Draw shadow effect, if required */
if (shadow)
{
/* Sanity check */
if ( (shadow->type != GFX_THUMBNAIL_SHADOW_NONE)
&& (shadow->alpha > 0.0f))
{
float shadow_width;
float shadow_height;
float shadow_x;
float shadow_y;
float shadow_color[16] = {
0.0f, 0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f, 1.0f
};
float shadow_alpha = thumbnail_alpha;
/* Set shadow opacity */
if (shadow->alpha < 1.0f)
shadow_alpha *= shadow->alpha;
gfx_display_set_alpha(shadow_color, shadow_alpha);
/* Configure shadow based on effect type
* > Not using a switch() here, since we've
* already eliminated GFX_THUMBNAIL_SHADOW_NONE */
if (shadow->type == GFX_THUMBNAIL_SHADOW_OUTLINE)
{
shadow_width = draw_width + (float)(shadow->outline.width * 2);
shadow_height = draw_height + (float)(shadow->outline.width * 2);
shadow_x = draw_x - (float)shadow->outline.width;
shadow_y = draw_y - (float)shadow->outline.width;
}
/* Default: GFX_THUMBNAIL_SHADOW_DROP */
else
{
shadow_width = draw_width;
shadow_height = draw_height;
shadow_x = draw_x + shadow->drop.x_offset;
shadow_y = draw_y - shadow->drop.y_offset;
}
/* Apply shadow draw object configuration */
coords.color = (const float*)shadow_color;
draw.width = (unsigned)shadow_width;
draw.height = (unsigned)shadow_height;
draw.x = shadow_x;
draw.y = shadow_y;
/* Draw shadow */
if (draw.height > 0 && draw.width > 0)
if (dispctx->draw)
dispctx->draw(&draw, userdata, video_width, video_height);
}
}
/* Final thumbnail draw object configuration */
coords.color = (const float*)thumbnail_color;
draw.width = (unsigned)draw_width;
draw.height = (unsigned)draw_height;
draw.x = draw_x;
draw.y = draw_y;
/* Draw thumbnail */
if (draw.height > 0 && draw.width > 0)
if (dispctx->draw)
dispctx->draw(&draw, userdata, video_width, video_height);
if (dispctx->blend_end)
dispctx->blend_end(userdata);
}
}