From 15eddd52b9ae5f8b8e6b37cff1caeb641ff50c65 Mon Sep 17 00:00:00 2001 From: LibretroAdmin Date: Sat, 26 Sep 2026 14:45:46 +0000 Subject: [PATCH] Video: add Video Filter Threads setting CPU video filters always ran on one worker per detected core (RARCH_SOFTFILTER_THREADS_AUTO). That suits the heavy filters (NTSC, NTSC CRT, 2xBR) but over-parallelises the light ones: at 256x224 EPX is ~26% slower on eight workers than on one, and LQ2x/2xSaI lose their four-worker gain at eight. video_filter_threads (Settings > Video > Output, under Video Filter; advanced) picks the count: 0 is Automatic, the previous behaviour and the default; 1-16 forces that many workers. Changing it while a filter is running rebuilds the filter on the new count at once; with no filter loaded the value applies at the next load. video_driver_init_filter() now frees a live filter before building the new one. Re-init while content was running (the new setting, and also a video filter path change) previously overwrote state_filter and state_buffer, leaking the old filter, its worker pool and its output buffer. tools/settings_table.reference is regenerated; it had drifted on master (HDR, custom viewport and window size rows) independently of this change. --- config.def.h | 5 ++ configuration.h | 1 + gfx/video_driver.c | 7 ++- menu/cbs/menu_cbs_sublabel.c | 3 ++ menu/menu_displaylist.c | 5 ++ menu/menu_setting.c | 20 ++++++++ msg_hash_lbl_str.h | 1 + settings/settings_def_video_filter_rotation.h | 5 ++ tools/settings_table.reference | 50 +++++++++++-------- 9 files changed, 74 insertions(+), 23 deletions(-) diff --git a/config.def.h b/config.def.h index 53cb3ca0ff..68fa5623d7 100644 --- a/config.def.h +++ b/config.def.h @@ -514,6 +514,11 @@ #define MINIMUM_SWAP_INTERVAL 1 #define MAXIMUM_SWAP_INTERVAL 16 +/* Worker threads a CPU video filter runs on; 0 (Automatic) uses one + * per detected CPU core. */ +#define DEFAULT_VIDEO_FILTER_THREADS 0 +#define MAXIMUM_VIDEO_FILTER_THREADS 16 + /* Threaded video: the core runs on one thread and the video driver * presents on another. Off by default, as it has always been; the * Switch keeps its own default. When it is on, hardware-rendered cores diff --git a/configuration.h b/configuration.h index bf20d84669..d285cd41c4 100644 --- a/configuration.h +++ b/configuration.h @@ -258,6 +258,7 @@ typedef struct settings unsigned video_scale_integer_axis; unsigned video_scale_integer_scaling; unsigned video_max_swapchain_images; + unsigned video_filter_threads; unsigned video_swap_interval; unsigned video_hard_sync_frames; unsigned video_frame_delay; diff --git a/gfx/video_driver.c b/gfx/video_driver.c index 27756c9776..e8c70ba590 100644 --- a/gfx/video_driver.c +++ b/gfx/video_driver.c @@ -2358,6 +2358,11 @@ void video_driver_init_filter(enum retro_pixel_format colfmt_int, video_thread_wait_idle(); #endif + /* Re-init (a new filter path, a new thread count) replaces the live + * filter: release it and its output buffer rather than orphan them */ + if (video_st->state_filter) + video_driver_filter_free(); + if (video_driver_is_hw_context()) { RARCH_WARN("[Video] Cannot use CPU filters when hardware rendering is used.\n"); @@ -2366,7 +2371,7 @@ void video_driver_init_filter(enum retro_pixel_format colfmt_int, if (!(video_st->state_filter = rarch_softfilter_new( settings->paths.path_softfilter_plugin, - RARCH_SOFTFILTER_THREADS_AUTO, colfmt, dims))) + settings->uints.video_filter_threads, colfmt, dims))) { RARCH_ERR("[Video] Failed to load filter.\n"); return; diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index aa912cfe2b..95cda7809c 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -2320,6 +2320,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, { MENU_ENUM_LABEL_CHEEVOS_PASSWORD, MENU_ENUM_SUBLABEL_CHEEVOS_PASSWORD }, { MENU_ENUM_LABEL_VIDEO_FILTER, MENU_ENUM_SUBLABEL_VIDEO_FILTER }, { MENU_ENUM_LABEL_VIDEO_FILTER_REMOVE, MENU_ENUM_SUBLABEL_VIDEO_FILTER_REMOVE }, +#ifdef HAVE_VIDEO_FILTER + { MENU_ENUM_LABEL_VIDEO_FILTER_THREADS, MENU_ENUM_SUBLABEL_VIDEO_FILTER_THREADS }, +#endif { MENU_ENUM_LABEL_VIDEO_CROP_OVERSCAN, MENU_ENUM_SUBLABEL_VIDEO_CROP_OVERSCAN }, { MENU_ENUM_LABEL_VIDEO_SMOOTH, MENU_ENUM_SUBLABEL_VIDEO_SMOOTH }, { MENU_ENUM_LABEL_VIDEO_FONT_ENABLE, MENU_ENUM_SUBLABEL_VIDEO_FONT_ENABLE }, diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 9ed7f4cb50..b0d28864f2 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -9532,6 +9532,11 @@ unsigned menu_displaylist_build_list( PARSE_ONLY_PATH, false) == 0) count++; + if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, + MENU_ENUM_LABEL_VIDEO_FILTER_THREADS, + PARSE_ONLY_UINT, false) == 0) + count++; + if (*settings->paths.path_softfilter_plugin) if (menu_entries_append(list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_FILTER_REMOVE), diff --git a/menu/menu_setting.c b/menu/menu_setting.c index a638b30bc7..88955f2f3c 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -7070,6 +7070,18 @@ static size_t setting_get_string_representation_video_swap_interval(rarch_settin return snprintf(s, len, "%u", *setting->value.target.unsigned_integer); } +#ifdef HAVE_VIDEO_FILTER +static size_t setting_get_string_representation_uint_video_filter_threads( + rarch_setting_t *setting, char *s, size_t len) +{ + if (!setting) + return 0; + if (*setting->value.target.unsigned_integer == 0) + return strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_AUTO), len); + return snprintf(s, len, "%u", *setting->value.target.unsigned_integer); +} +#endif + static size_t setting_get_string_representation_black_frame_insertion(rarch_setting_t *setting, char *s, size_t len) { @@ -9694,6 +9706,14 @@ static void general_write_handler(rarch_setting_t *setting) log_dir); } break; +#ifdef HAVE_VIDEO_FILTER + case MENU_ENUM_LABEL_VIDEO_FILTER_THREADS: + /* Rebuild a running filter on the new worker count; with no + * filter loaded the value is picked up at the next load. */ + if (video_state_get_ptr()->state_filter) + command_event(CMD_EVENT_VIDEO_FILTER_INIT, NULL); + break; +#endif case MENU_ENUM_LABEL_VIDEO_SMOOTH: case MENU_ENUM_LABEL_VIDEO_CTX_SCALING: #if defined(DINGUX) diff --git a/msg_hash_lbl_str.h b/msg_hash_lbl_str.h index 02cf70cdc0..417898ce26 100644 --- a/msg_hash_lbl_str.h +++ b/msg_hash_lbl_str.h @@ -1630,6 +1630,7 @@ #define MENU_ENUM_LABEL_VIDEO_FILTER_STR "video_filter" #define MENU_ENUM_LABEL_VIDEO_FILTER_DIR_STR "video_filter_dir" #define MENU_ENUM_LABEL_VIDEO_FILTER_ENABLE_STR "filter_enable" +#define MENU_ENUM_LABEL_VIDEO_FILTER_THREADS_STR "video_filter_threads" #define MENU_ENUM_LABEL_VIDEO_FONT_ENABLE_STR "video_font_enable" #define MENU_ENUM_LABEL_VIDEO_FONT_PATH_STR "video_font_path" #define MENU_ENUM_LABEL_VIDEO_FONT_SIZE_STR "video_font_size" diff --git a/settings/settings_def_video_filter_rotation.h b/settings/settings_def_video_filter_rotation.h index bc0cd0e2c9..04bcf28088 100644 --- a/settings/settings_def_video_filter_rotation.h +++ b/settings/settings_def_video_filter_rotation.h @@ -26,4 +26,9 @@ S_BOOL(video_filter_enable, VIDEO_FILTER_ENABLE, "Video Filter Enable", "Apply Video Filter. Is a hint that does not necessarily have to be honored by the video driver.") #endif +S_UINT(video_filter_threads, VIDEO_FILTER_THREADS, + "video_filter_threads", + DEFAULT_VIDEO_FILTER_THREADS, SD_FLAG_ADVANCED, SDESC_RANGE_MINMAX, CMD_EVENT_NONE, 0, MAXIMUM_VIDEO_FILTER_THREADS, 1, 0, setting_action_ok_uint, setting_get_string_representation_uint_video_filter_threads, + "Video Filter Threads", + "How many threads a CPU video filter may run on. 'Automatic' uses one per CPU core. Light filters can run slower when spread over many threads, so a lower count may be faster for them; heavy filters such as NTSC gain the most from more threads. A running filter picks up the change straight away.") #endif diff --git a/tools/settings_table.reference b/tools/settings_table.reference index 31caebf13b..c8fb8ea701 100644 --- a/tools/settings_table.reference +++ b/tools/settings_table.reference @@ -224,6 +224,8 @@ bool pause_on_disconnect 1 0 bool video_frame_delay_auto 1 0 bool audio_fastpath_s16 1 0 bool audio_resampler_hq_oversampling 1 0 +bool video_hdr_use_display_peak 1 0 +bool video_hdr_send_luminance 1 0 bool video_window_custom_size_enable 1 0 bool input_overlay_auto_scale 1 1 bool video_font_enable 1 1 @@ -405,6 +407,10 @@ bool netplay_request_device_p13 1 0 bool netplay_request_device_p14 1 0 bool netplay_request_device_p15 1 0 bool netplay_request_device_p16 1 0 +int custom_viewport_x 0 0 +int custom_viewport_y 0 0 +int video_windowed_position_x 1 0 +int video_windowed_position_y 1 0 int crt_switch_center_adjust 0 0 int crt_switch_porch_adjust 0 0 int crt_switch_vertical_adjust 0 0 @@ -436,12 +442,10 @@ uint microphone_latency 0 0 uint microphone_rate 1 48000 uint custom_viewport_width 0 0 uint custom_viewport_height 0 0 -uint custom_viewport_x 0 0 -uint custom_viewport_y 0 0 -uint video_windowed_position_x 1 0 -uint video_windowed_position_y 1 0 uint video_windowed_position_width 1 1280 uint video_windowed_position_height 1 720 +uint video_window_auto_width_max 1 1920 +uint video_window_auto_height_max 1 1080 uint video_fse_negotiation 1 VIDEO_FSE_RELAXED uint video_fullscreen_x 1 0 uint video_fullscreen_y 1 0 @@ -577,8 +581,6 @@ uint microphone_block_frames 1 0 uint menu_thumbnail_preview_threads 1 1 uint menu_file_browser_extension_display 1 MENU_FILE_BROWSER_EXTENSION_DISPLAY_ALWAYS uint video_scale 1 3 -uint video_window_auto_width_max 1 1920 -uint video_window_auto_height_max 1 1080 uint video_window_opacity 1 100 uint video_stream_port 1 56400 uint video_record_scale_factor 1 1 @@ -889,6 +891,8 @@ bool pause_on_disconnect 1 0 bool video_frame_delay_auto 1 0 bool audio_fastpath_s16 1 0 bool audio_resampler_hq_oversampling 1 0 +bool video_hdr_use_display_peak 1 0 +bool video_hdr_send_luminance 1 0 bool video_window_custom_size_enable 1 0 bool input_overlay_auto_scale 1 0 bool video_font_enable 1 1 @@ -1069,6 +1073,10 @@ bool netplay_request_device_p13 1 0 bool netplay_request_device_p14 1 0 bool netplay_request_device_p15 1 0 bool netplay_request_device_p16 1 0 +int custom_viewport_x 0 0 +int custom_viewport_y 0 0 +int video_windowed_position_x 1 0 +int video_windowed_position_y 1 0 int crt_switch_center_adjust 0 0 int crt_switch_porch_adjust 0 0 int crt_switch_vertical_adjust 0 0 @@ -1099,12 +1107,10 @@ uint microphone_latency 0 0 uint microphone_rate 1 48000 uint custom_viewport_width 0 0 uint custom_viewport_height 0 0 -uint custom_viewport_x 0 0 -uint custom_viewport_y 0 0 -uint video_windowed_position_x 1 0 -uint video_windowed_position_y 1 0 uint video_windowed_position_width 1 1280 uint video_windowed_position_height 1 720 +uint video_window_auto_width_max 1 1920 +uint video_window_auto_height_max 1 1080 uint video_fse_negotiation 1 VIDEO_FSE_RELAXED uint video_fullscreen_x 1 0 uint video_fullscreen_y 1 0 @@ -1238,8 +1244,6 @@ uint microphone_block_frames 1 0 uint menu_thumbnail_preview_threads 1 1 uint menu_file_browser_extension_display 1 MENU_FILE_BROWSER_EXTENSION_DISPLAY_ALWAYS uint video_scale 1 3 -uint video_window_auto_width_max 1 1920 -uint video_window_auto_height_max 1 1080 uint video_window_opacity 1 100 uint video_stream_port 1 56400 uint video_record_scale_factor 1 1 @@ -1559,6 +1563,8 @@ bool pause_on_disconnect 1 0 bool video_frame_delay_auto 1 0 bool audio_fastpath_s16 1 0 bool audio_resampler_hq_oversampling 1 0 +bool video_hdr_use_display_peak 1 0 +bool video_hdr_send_luminance 1 0 bool video_window_custom_size_enable 1 0 bool input_overlay_auto_scale 1 0 bool video_font_enable 1 1 @@ -1740,6 +1746,10 @@ bool netplay_request_device_p13 1 0 bool netplay_request_device_p14 1 0 bool netplay_request_device_p15 1 0 bool netplay_request_device_p16 1 0 +int custom_viewport_x 0 0 +int custom_viewport_y 0 0 +int video_windowed_position_x 1 0 +int video_windowed_position_y 1 0 int crt_switch_center_adjust 0 0 int crt_switch_porch_adjust 0 0 int crt_switch_vertical_adjust 0 0 @@ -1771,12 +1781,14 @@ uint microphone_latency 0 0 uint microphone_rate 1 48000 uint custom_viewport_width 0 0 uint custom_viewport_height 0 0 -uint custom_viewport_x 0 0 -uint custom_viewport_y 0 0 -uint video_windowed_position_x 1 0 -uint video_windowed_position_y 1 0 uint video_windowed_position_width 1 1280 uint video_windowed_position_height 1 720 +uint video_window_auto_width_max 1 1920 +uint video_window_auto_height_max 1 1080 +uint desktop_menu_window_x 1 0 +uint desktop_menu_window_y 1 0 +uint desktop_menu_window_width 1 0 +uint desktop_menu_window_height 1 0 uint video_fse_negotiation 1 VIDEO_FSE_RELAXED uint video_fullscreen_x 1 0 uint video_fullscreen_y 1 0 @@ -1844,10 +1856,6 @@ uint desktop_menu_icon_view_zoom 1 50 uint desktop_menu_all_playlists_list_max_count 1 0 uint desktop_menu_all_playlists_grid_max_count 1 0 uint desktop_menu_theme 1 0 -uint desktop_menu_window_x 1 0 -uint desktop_menu_window_y 1 0 -uint desktop_menu_window_width 1 0 -uint desktop_menu_window_height 1 0 uint ai_service_source_lang 1 0 uint ai_service_target_lang 1 0 uint video_scale_integer_axis 1 0 @@ -1925,8 +1933,6 @@ uint microphone_block_frames 1 0 uint menu_thumbnail_preview_threads 1 1 uint menu_file_browser_extension_display 1 MENU_FILE_BROWSER_EXTENSION_DISPLAY_ALWAYS uint video_scale 1 3 -uint video_window_auto_width_max 1 1920 -uint video_window_auto_height_max 1 1080 uint video_window_opacity 1 100 uint video_stream_port 1 56400 uint video_record_scale_factor 1 1