@@ -2511,47 +2511,71 @@ void video_viewport_get_scaled_integer(struct video_viewport *vp,
25112511 uint8_t max_scale_w = 1 ;
25122512 uint8_t max_scale_h = 1 ;
25132513
2514- /* Overscale if less screen is lost by cropping instead of empty added by underscale */
2514+ /* Overscale if only overscan or a small number of rows get cropped.
2515+ * Otherwise, underscale if the smallest margin doesn't exceed 12%.
2516+ * Otherwise, fall back to non-integer scaling. */
25152517 if (scaling == VIDEO_SCALE_INTEGER_SCALING_SMART )
25162518 {
2517- unsigned overscale_w = (width / content_width ) + !!(width % content_width );
2518- unsigned underscale_w = (width / content_width );
2519- unsigned overscale_h = (height / content_height ) + !!(height % content_height );
2520- unsigned underscale_h = (height / content_height );
2521- int overscale_w_diff = (content_width * overscale_w ) - width ;
2522- int underscale_w_diff = width - (content_width * underscale_w );
2523- int overscale_h_diff = (content_height * overscale_h ) - height ;
2524- int underscale_h_diff = height - (content_height * underscale_h );
2525- int scale_h_diff = overscale_h_diff - underscale_h_diff ;
2526-
2527- max_scale_w = underscale_w ;
2528- max_scale_h = underscale_h ;
2529-
2530- /* Prefer nearest scale */
2531- if (overscale_w_diff <= underscale_w_diff )
2532- max_scale_w = overscale_w ;
2533-
2534- if (overscale_h_diff <= underscale_h_diff )
2519+ unsigned max_scale_w = width / content_width ;
2520+ unsigned underscale_h = height / content_height ;
2521+ /* Always check if the next integer factor results in a usable
2522+ * overscale even if the underscale factor already fills the screen.
2523+ * This is particularly relevant when scaling 240p content to 4k -
2524+ * a x9 scale will fill the height, but a x10 overscale will only
2525+ * crop overscan and fills more of the screen. */
2526+ unsigned overscale_h = underscale_h + 1 ;
2527+ unsigned max_scale_h = underscale_h ;
2528+ float width_utilization ;
2529+ float height_utilization ;
2530+
2531+ /* This is the minimum amount content that must be shown in order
2532+ * for overscan to be used.
2533+ *
2534+ * Handhelds don't have overscan, but there are noteworthy cases
2535+ * where overscaling crops so few pixels that it's worth allowing:
2536+ * - Atari Lynx @ 800p
2537+ * - GBA @ 1080p or 4k
2538+ * - PSP @ 800p, 1080p or 4k
2539+ * 6 pixels has no special significance, it's simply the smallest
2540+ * value that covers all of the above cases. */
2541+ unsigned overscale_min_height = content_height - 6 ;
2542+ /* Overscale 240p content if only the overscan area gets cropped.
2543+ * The core may have applied some cropping, or the emulated console
2544+ * may not use all 240 lines, so the content height may be lower.
2545+ * 192 is 80% of 240, and is the title safe area used by Nintendo
2546+ * for NES development. See https://www.nesdev.org/wiki/Overscan */
2547+ if (192 <= content_height && content_height <= 240 )
2548+ overscale_min_height = 192 ;
2549+ /* Use the 240p thresholds for 480p, just doubled. */
2550+ else if (192 * 2 <= content_height && content_height <= 480 )
2551+ overscale_min_height = 192 * 2 ;
2552+
2553+ if (height / overscale_h >= overscale_min_height )
25352554 max_scale_h = overscale_h ;
2536-
2537- /* Limit width overscale */
2538- if (max_scale_w * content_width >= width + ((int )content_width / 2 ))
2539- max_scale_w = underscale_w ;
2540-
2541- /* Allow overscale when it is close enough */
2542- if (scale_h_diff > 0 && scale_h_diff < 64 )
2543- max_scale_h = overscale_h ;
2544- /* Overscale will be too much even if it is closer */
2545- else if ((scale_h_diff < -140 && scale_h_diff >= (int )- content_height / 2 )
2546- || (scale_h_diff < -30 && scale_h_diff > -50 )
2547- || (scale_h_diff > 20 ))
2548- max_scale_h = underscale_h ;
2549-
2550- /* Sensible limiting for small sources */
2551- if (content_height <= 200 )
2552- max_scale_h = underscale_h ;
2553-
25542555 max_scale = MIN (max_scale_w , max_scale_h );
2556+
2557+ /* Final step: check if the underscaling margins are too large.
2558+ *
2559+ * Sometimes there's no reasonable integer scale that can be used,
2560+ * such as when scaling 480p to 720p. Overscaling would crop 25% of
2561+ * the content height, but underscaling would only use 2/3 of the
2562+ * display's height.
2563+ *
2564+ * A threshold of 88% utilization (i.e. 12% margin) captures the
2565+ * majority of underscaling scenarios for HD resolutions while
2566+ * keeping the margins relatively small. Noteworthy use cases that
2567+ * straddle this cutoff include: GBA scaled to 720p; Nintendo DS
2568+ * scaled to 1080p; and 480p content scaled to 1080p. Past this,
2569+ * noticeable jumps in margin size would be needed to capture a
2570+ * relatively small number of additional use cases.
2571+ * TODO: Make this threshold configurable. */
2572+ width_utilization = (float )content_width * max_scale / width ;
2573+ height_utilization = (float )content_height * max_scale / height ;
2574+ if (MAX (height_utilization , width_utilization ) < 0.88 )
2575+ {
2576+ video_viewport_get_scaled_aspect (vp , width , height , y_down );
2577+ return ;
2578+ }
25552579 }
25562580 else if (scaling == VIDEO_SCALE_INTEGER_SCALING_OVERSCALE )
25572581 max_scale = MIN ((width / content_width ) + !!(width % content_width ),
0 commit comments