diff --git a/gfx/gfx_widgets.c b/gfx/gfx_widgets.c index 7118c76bf5..b8597251c5 100644 --- a/gfx/gfx_widgets.c +++ b/gfx/gfx_widgets.c @@ -339,7 +339,6 @@ unsigned gfx_widgets_get_height(void) return simple_widget_height; } - unsigned gfx_widgets_get_generic_message_height(void) { return generic_message_height; @@ -355,6 +354,11 @@ unsigned gfx_widgets_get_last_video_height(void) return last_video_height; } +size_t gfx_widgets_get_msg_queue_size(void) +{ + return current_msgs ? current_msgs->size : 0; +} + /* Widgets list */ const static gfx_widget_t* const widgets[] = { &gfx_widget_screenshot, @@ -1500,6 +1504,7 @@ void gfx_widgets_frame(void *data) video_frame_info_t *video_info; bool framecount_show; bool memory_show; + bool core_status_msg_show; void *userdata; unsigned video_width; unsigned video_height; @@ -1521,6 +1526,7 @@ void gfx_widgets_frame(void *data) video_info = (video_frame_info_t*)data; framecount_show = video_info->framecount_show; memory_show = video_info->memory_show; + core_status_msg_show = video_info->core_status_msg_show; userdata = video_info->userdata; video_width = video_info->width; video_height = video_info->height; @@ -1705,26 +1711,11 @@ void gfx_widgets_frame(void *data) } #endif - /* Draw all messages */ - for (i = 0; i < current_msgs->size; i++) - { - menu_widget_msg_t *msg = (menu_widget_msg_t*)current_msgs->list[i].userdata; - - if (!msg) - continue; - - if (msg->task_ptr) - gfx_widgets_draw_task_msg(msg, userdata, - video_width, video_height); - else - gfx_widgets_draw_regular_msg(msg, userdata, - video_width, video_height); - } - /* FPS Counter */ if ( fps_show || framecount_show || memory_show + || core_status_msg_show ) { const char *text = *gfx_widgets_fps_text == '\0' ? "N/A" : gfx_widgets_fps_text; @@ -1798,6 +1789,22 @@ void gfx_widgets_frame(void *data) widget->frame(data); } + /* Draw all messages */ + for (i = 0; i < current_msgs->size; i++) + { + menu_widget_msg_t *msg = (menu_widget_msg_t*)current_msgs->list[i].userdata; + + if (!msg) + continue; + + if (msg->task_ptr) + gfx_widgets_draw_task_msg(msg, userdata, + video_width, video_height); + else + gfx_widgets_draw_regular_msg(msg, userdata, + video_width, video_height); + } + #ifdef HAVE_MENU /* Load content animation */ if (load_content_animation_running) diff --git a/gfx/gfx_widgets.h b/gfx/gfx_widgets.h index 3266a94b57..080b0aa4d4 100644 --- a/gfx/gfx_widgets.h +++ b/gfx/gfx_widgets.h @@ -113,6 +113,8 @@ float* gfx_widgets_get_backdrop_orig(void); unsigned gfx_widgets_get_last_video_width(void); unsigned gfx_widgets_get_last_video_height(void); unsigned gfx_widgets_get_generic_message_height(void); +/* Warning: not thread safe! */ +size_t gfx_widgets_get_msg_queue_size(void); float gfx_widgets_get_thumbnail_scale_factor( const float dst_width, const float dst_height, diff --git a/gfx/widgets/gfx_widget_generic_message.c b/gfx/widgets/gfx_widget_generic_message.c index 209f784eab..e29008ce55 100644 --- a/gfx/widgets/gfx_widget_generic_message.c +++ b/gfx/widgets/gfx_widget_generic_message.c @@ -95,6 +95,7 @@ static void gfx_widget_generic_message_frame(void* data) unsigned height = gfx_widgets_get_generic_message_height(); unsigned text_color = COLOR_TEXT_ALPHA(0xffffffff, (unsigned)(state->alpha*255.0f)); gfx_widget_font_data_t* font_regular = gfx_widgets_get_font_regular(); + size_t msg_queue_size = gfx_widgets_get_msg_queue_size(); gfx_display_set_alpha(gfx_widgets_get_backdrop_orig(), state->alpha); @@ -111,6 +112,11 @@ static void gfx_widget_generic_message_frame(void* data) video_width, video_height, text_color, TEXT_ALIGN_CENTER, false); + + /* If the message queue is active, must flush the + * text here to avoid overlaps */ + if (msg_queue_size > 0) + gfx_widgets_flush_text(video_width, video_height, font_regular); } } diff --git a/gfx/widgets/gfx_widget_libretro_message.c b/gfx/widgets/gfx_widget_libretro_message.c index 009e58bf97..5042888e8f 100644 --- a/gfx/widgets/gfx_widget_libretro_message.c +++ b/gfx/widgets/gfx_widget_libretro_message.c @@ -103,6 +103,7 @@ static void gfx_widget_libretro_message_frame(void *data) float* backdrop_orign = gfx_widgets_get_backdrop_orig(); unsigned text_color = COLOR_TEXT_ALPHA(0xffffffff, (unsigned)(state->alpha*255.0f)); gfx_widget_font_data_t* font_regular = gfx_widgets_get_font_regular(); + size_t msg_queue_size = gfx_widgets_get_msg_queue_size(); gfx_display_set_alpha(backdrop_orign, state->alpha); @@ -119,6 +120,11 @@ static void gfx_widget_libretro_message_frame(void *data) video_width, video_height, text_color, TEXT_ALIGN_LEFT, false); + + /* If the message queue is active, must flush the + * text here to avoid overlaps */ + if (msg_queue_size > 0) + gfx_widgets_flush_text(video_width, video_height, font_regular); } } diff --git a/libretro-common/include/libretro.h b/libretro-common/include/libretro.h index ff32ca2300..f3c08f35b5 100644 --- a/libretro-common/include/libretro.h +++ b/libretro-common/include/libretro.h @@ -2544,13 +2544,72 @@ enum retro_message_target RETRO_MESSAGE_TARGET_LOG }; +enum retro_message_type +{ + RETRO_MESSAGE_TYPE_NOTIFICATION = 0, + RETRO_MESSAGE_TYPE_NOTIFICATION_ALT, + RETRO_MESSAGE_TYPE_STATUS +}; + struct retro_message_ext { - const char *msg; /* Message to be displayed/logged */ - unsigned frames; /* Duration in frames of message when targeting OSD */ - unsigned priority; /* Message priority when targeting OSD */ - enum retro_log_level level; /* Message logging level (info, warn, etc.) */ - enum retro_message_target target; /* Message destination: OSD, logging interface or both */ + /* Message string to be displayed/logged */ + const char *msg; + /* Duration (in ms) of message when targeting the OSD */ + unsigned duration; + /* Message priority when targeting the OSD + * > When multiple concurrent messages are sent to + * the frontend and the frontend does not have the + * capacity to display them all, messages with the + * *highest* priority value should be shown + * > There is no upper limit to a message priority + * value (within the bounds of the unsigned data type) + * > In the reference frontend (RetroArch), the same + * priority values are used for frontend-generated + * notifications, which are typically assigned values + * between 0 and 3 depending upon importance */ + unsigned priority; + /* Message logging level (info, warn, error, etc.) */ + enum retro_log_level level; + /* Message destination: OSD, logging interface or both */ + enum retro_message_target target; + /* Message 'type' when targeting the OSD + * > RETRO_MESSAGE_TYPE_NOTIFICATION: Specifies that a + * message should be handled in identical fashion to + * a standard frontend-generated notification + * > RETRO_MESSAGE_TYPE_NOTIFICATION_ALT: Specifies that + * message is a notification that requires user attention + * or action, but that it should be displayed in a manner + * that differs from standard frontend-generated notifications. + * This would typically correspond to messages that should be + * displayed immediately (independently from any internal + * frontend message queue), and/or which should be visually + * distinguishable from frontend-generated notifications. + * For example, a core may wish to inform the user of + * information related to a disk-change event. It is + * expected that the frontend itself may provide a + * notification in this case; if the core sends a + * message of type RETRO_MESSAGE_TYPE_NOTIFICATION, an + * uncomfortable 'double-notification' may occur. A message + * of RETRO_MESSAGE_TYPE_NOTIFICATION_ALT should therefore + * be presented such that visual conflict with regular + * notifications does not occur + * > RETRO_MESSAGE_TYPE_STATUS: Indicates that message + * is not a standard notification. This typically + * corresponds to 'status' indicators, such as a core's + * internal FPS, which are intended to be displayed + * either permanently while a core is running, or in + * a manner that does not suggest user attention or action + * is required. 'Status' type messages should therefore be + * displayed in a different on-screen location and in a manner + * easily distinguishable from both standard frontend-generated + * notifications and messages of type RETRO_MESSAGE_TYPE_NOTIFICATION_ALT + * NOTE: Message type is a *hint*, and may be ignored + * by the frontend. If a frontend lacks support for + * displaying messages via alternate means than standard + * frontend-generated notifications, it will treat *all* + * messages as having the type RETRO_MESSAGE_TYPE_NOTIFICATION */ + enum retro_message_type type; }; /* Describes how the libretro implementation maps a libretro input bind diff --git a/retroarch.c b/retroarch.c index 7b3814dc99..8c8b3f7c7b 100644 --- a/retroarch.c +++ b/retroarch.c @@ -1446,6 +1446,14 @@ typedef struct runloop_ctx_msg_info bool flush; } runloop_ctx_msg_info_t; +typedef struct +{ + char str[128]; + unsigned priority; + float duration; + bool set; +} runloop_core_status_msg_t; + struct rarch_dir_list { struct string_list *list; @@ -1772,6 +1780,14 @@ static retro_keyboard_event_t runloop_frontend_key_event = NULL; static core_option_manager_t *runloop_core_options = NULL; static msg_queue_t *runloop_msg_queue = NULL; +static runloop_core_status_msg_t runloop_core_status_msg = +{ + "", + 0, + 0.0f, + false +}; + static retro_usec_t runloop_frame_time_last = 0; #ifdef HAVE_DISCORD @@ -10324,7 +10340,8 @@ static bool rarch_environment_cb(unsigned cmd, void *data) case RETRO_ENVIRONMENT_SET_MESSAGE_EXT: { - const struct retro_message_ext *msg = (const struct retro_message_ext*)data; + const struct retro_message_ext *msg = (const struct retro_message_ext*)data; + struct retro_system_av_info *av_info = &video_driver_av_info; /* Log message, if required */ if (msg->target != RETRO_MESSAGE_TARGET_OSD) @@ -10356,15 +10373,60 @@ static bool rarch_environment_cb(unsigned cmd, void *data) /* Display message via OSD, if required */ if (msg->target != RETRO_MESSAGE_TARGET_LOG) { + /* Handle 'status' messages */ + if (msg->type == RETRO_MESSAGE_TYPE_STATUS) + { + /* Note: We need to lock a mutex here. Strictly + * speaking, runloop_core_status_msg is not part + * of the message queue, but: + * - It may be implemented as a queue in the future + * - It seems unnecessary to create a new slock_t + * object for this type of message when + * _runloop_msg_queue_lock is already available + * We therefore just call runloop_msg_queue_lock()/ + * runloop_msg_queue_unlock() in this case */ + runloop_msg_queue_lock(); + + /* If a message is already set, only overwrite + * it if the new message has the same or higher + * priority */ + if (!runloop_core_status_msg.set || + (runloop_core_status_msg.priority <= msg->priority)) + { + if (!string_is_empty(msg->msg)) + { + strlcpy(runloop_core_status_msg.str, msg->msg, + sizeof(runloop_core_status_msg.str)); + + runloop_core_status_msg.duration = (float)msg->duration; + runloop_core_status_msg.set = true; + } + else + { + /* Ensure sane behaviour if core sends an + * empty message */ + runloop_core_status_msg.str[0] = '\0'; + runloop_core_status_msg.priority = 0; + runloop_core_status_msg.duration = 0.0f; + runloop_core_status_msg.set = false; + } + } + + runloop_msg_queue_unlock(); + } + /* Handle 'alternate' non-queued notifications */ #if defined(HAVE_GFX_WIDGETS) - if (gfx_widgets_active()) - gfx_widget_set_libretro_message(msg->msg, - roundf((float)msg->frames / 60.0f * 1000.0f)); - else + else if ((msg->type == RETRO_MESSAGE_TYPE_NOTIFICATION_ALT) && + gfx_widgets_active()) + gfx_widget_set_libretro_message(msg->msg, msg->duration); #endif + /* Handle standard (queued) notifications */ + else { enum message_queue_category category; + unsigned duration_frames = 0; + /* Assign category */ switch (msg->level) { case RETRO_LOG_WARN: @@ -10380,8 +10442,13 @@ static bool rarch_environment_cb(unsigned cmd, void *data) break; } + /* Get duration in frames */ + if (av_info) + duration_frames = (unsigned)((av_info->timing.fps * + (float)msg->duration / 1000.0f) + 0.5f); + runloop_msg_queue_push(msg->msg, - msg->priority, msg->frames, + msg->priority, duration_frames, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, category); } @@ -22978,6 +23045,52 @@ static void video_driver_frame(const void *data, unsigned width, video_driver_window_title_update = true; } + /* Add core status message to 'fps_text' string + * TODO/FIXME: fps_text is used for several status + * parameters, not just FPS. It should probably be + * renamed to reflect this... */ + if (video_info.core_status_msg_show) + { + /* Note: We need to lock a mutex here. Strictly + * speaking, runloop_core_status_msg is not part + * of the message queue, but: + * - It may be implemented as a queue in the future + * - It seems unnecessary to create a new slock_t + * object for this type of message when + * _runloop_msg_queue_lock is already available + * We therefore just call runloop_msg_queue_lock()/ + * runloop_msg_queue_unlock() in this case */ + runloop_msg_queue_lock(); + + /* Check whether duration timer has elapsed */ + runloop_core_status_msg.duration -= gfx_animation_get_delta_time(); + + if (runloop_core_status_msg.duration < 0.0f) + { + runloop_core_status_msg.str[0] = '\0'; + runloop_core_status_msg.priority = 0; + runloop_core_status_msg.duration = 0.0f; + runloop_core_status_msg.set = false; + } + else + { + /* If 'fps_text' is already set, add status + * message at the end */ + if (!string_is_empty(fps_text)) + { + strlcat(fps_text, + " || ", sizeof(fps_text)); + strlcat(fps_text, + runloop_core_status_msg.str, sizeof(fps_text)); + } + else + strlcpy(fps_text, runloop_core_status_msg.str, + sizeof(fps_text)); + } + + runloop_msg_queue_unlock(); + } + /* Slightly messy code, * but we really need to do processing before blocking on VSync * for best possible scheduling. @@ -23139,6 +23252,7 @@ static void video_driver_frame(const void *data, unsigned width, if ( video_info.fps_show || video_info.framecount_show || video_info.memory_show + || video_info.core_status_msg_show ) { #if defined(HAVE_GFX_WIDGETS) @@ -23289,6 +23403,7 @@ void video_driver_build_info(video_frame_info_t *video_info) video_info->memory_show = settings->bools.video_memory_show; video_info->statistics_show = settings->bools.video_statistics_show; video_info->framecount_show = settings->bools.video_framecount_show; + video_info->core_status_msg_show = runloop_core_status_msg.set; video_info->aspect_ratio_idx = settings->uints.video_aspect_ratio_idx; video_info->post_filter_record = settings->bools.video_post_filter_record; video_info->input_menu_swap_ok_cancel_buttons = settings->bools.input_menu_swap_ok_cancel_buttons; diff --git a/retroarch.h b/retroarch.h index 71e343b335..5147de4330 100644 --- a/retroarch.h +++ b/retroarch.h @@ -1098,6 +1098,7 @@ typedef struct video_frame_info bool memory_show; bool statistics_show; bool framecount_show; + bool core_status_msg_show; bool post_filter_record; bool windowed_fullscreen; bool fullscreen;