Add --render-fit feature

Add an option to configure how the rendering fits the window.

The default, `--render-fit=letterbox`, preserves the aspect ratio
and fits the window as best as possible, adding black bars at
the top/bottom or left/right if needed. This has been the only behavior
scrcpy supported so far.

Another mode, `--render-fit=disabled`, renders the display at the
top-left corner without scaling. This mode will be useful for virtual
display resizing.
This commit is contained in:
Romain Vimont 2026-04-07 21:14:48 +02:00
parent 3b068b669e
commit f7c5f71ea0
9 changed files with 76 additions and 2 deletions

View file

@ -80,6 +80,7 @@ _scrcpy() {
--record-format=
--record-orientation=
--render-driver=
--render-fit=
--require-audio
-s --serial=
-S --turn-screen-off
@ -176,6 +177,10 @@ _scrcpy() {
COMPREPLY=($(compgen -W 'direct3d opengl opengles2 opengles metal software' -- "$cur"))
return
;;
--render-fit)
COMPREPLY=($(compgen -W 'letterbox disabled' -- "$cur"))
return
;;
--shortcut-mod)
# Only auto-complete a single key
COMPREPLY=($(compgen -W 'lctrl rctrl lalt ralt lsuper rsuper' -- "$cur"))

View file

@ -85,6 +85,7 @@ arguments=(
'--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac flac wav)'
'--record-orientation=[Set the record orientation]:orientation values:(0 90 180 270)'
'--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)'
'--render-fit=[Set the render fit mode]:mode:(letterbox disabled)'
'--require-audio=[Make scrcpy fail if audio is enabled but does not work]'
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))'
{-S,--turn-screen-off}'[Turn the device screen off immediately]'

View file

@ -514,6 +514,17 @@ Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "me
<https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER>
.TP
.BI "\-\-render\-fit " mode
Set the render-fit mode to configure how the rendering fits the window.
Possible values are "letterbox" and "disabled":
- "letterbox": preserve the aspect ratio and fit the window as best as possible (black bars are added either at the top and bottom or at the sides if needed).
- "disabled": render the display at the top-left corner, without scaling.
Default is "letterbox".
.TP
.B \-\-require\-audio
By default, scrcpy mirrors only the video if audio capture fails on the device. This option makes scrcpy fail if audio is enabled but does not work.

View file

@ -105,6 +105,7 @@ enum {
OPT_CAMERA_ZOOM,
OPT_MIN_SIZE_ALIGNMENT,
OPT_NO_WINDOW_ASPECT_RATIO_LOCK,
OPT_RENDER_FIT,
};
struct sc_option {
@ -777,6 +778,20 @@ static const struct sc_option options[] = {
"\"opengles2\", \"opengles\", \"metal\" and \"software\".\n"
"<https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER>",
},
{
.longopt_id = OPT_RENDER_FIT,
.longopt = "render-fit",
.argdesc = "mode",
.text = "Set the render-fit mode to configure how the rendering fits "
"the window.\n"
"Possible values are \"letterbox\" and \"disabled\".\n"
"\"letterbox\": preserve the aspect ratio and fit the window "
"as best as possible (black bars are added either at the top "
"and bottom or at the sides if needed).\n"
"\"disabled\": render the display at the top-left corner, "
"without scaling.\n"
"Default is \"letterbox\".",
},
{
.longopt_id = OPT_REQUIRE_AUDIO,
.longopt = "require-audio",
@ -2322,6 +2337,22 @@ parse_mouse_bindings(const char *s, struct sc_mouse_bindings *mb) {
return true;
}
static bool
parse_render_fit(const char *optarg, enum sc_render_fit *mode) {
if (!strcmp(optarg, "letterbox")) {
*mode = SC_RENDER_FIT_LETTERBOX;
return true;
}
if (!strcmp(optarg, "disabled")) {
*mode = SC_RENDER_FIT_DISABLED;
return true;
}
LOGE("Unsupported render-fit: %s (expected letterbox or disabled)", optarg);
return false;
}
static bool
parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
const char *optstring, const struct option *longopts) {
@ -2755,6 +2786,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_NO_WINDOW_ASPECT_RATIO_LOCK:
opts->window_aspect_ratio_lock = false;
break;
case OPT_RENDER_FIT:
if (!parse_render_fit(optarg, &opts->render_fit)) {
return false;
}
break;
default:
// getopt prints the error message on stderr
return false;

View file

@ -59,6 +59,7 @@ const struct scrcpy_options scrcpy_options_default = {
.display_orientation = SC_ORIENTATION_0,
.record_orientation = SC_ORIENTATION_0,
.display_ime_policy = SC_DISPLAY_IME_POLICY_UNDEFINED,
.render_fit = SC_RENDER_FIT_LETTERBOX,
.window_x = SC_WINDOW_POSITION_UNDEFINED,
.window_y = SC_WINDOW_POSITION_UNDEFINED,
.window_width = 0,

View file

@ -220,6 +220,11 @@ enum sc_shortcut_mod {
SC_SHORTCUT_MOD_RSUPER = 1 << 5,
};
enum sc_render_fit {
SC_RENDER_FIT_LETTERBOX,
SC_RENDER_FIT_DISABLED,
};
struct sc_port_range {
uint16_t first;
uint16_t last;
@ -269,6 +274,7 @@ struct scrcpy_options {
enum sc_orientation display_orientation;
enum sc_orientation record_orientation;
enum sc_display_ime_policy display_ime_policy;
enum sc_render_fit render_fit;
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width;

View file

@ -814,6 +814,7 @@ aoa_complete:
.window_height = options->window_height,
.window_aspect_ratio_lock = options->window_aspect_ratio_lock,
.window_borderless = options->window_borderless,
.render_fit = options->render_fit,
.orientation = options->display_orientation,
.mipmaps = options->mipmaps,
.fullscreen = options->fullscreen,

View file

@ -158,7 +158,16 @@ sc_screen_is_relative_mode(struct sc_screen *screen) {
static void
compute_content_rect(struct sc_size render_size, struct sc_size content_size,
bool can_upscale, SDL_FRect *rect) {
bool can_upscale, enum sc_render_fit render_fit,
SDL_FRect *rect) {
if (render_fit == SC_RENDER_FIT_DISABLED) {
rect->x = 0;
rect->y = 0;
rect->w = content_size.width;
rect->h = content_size.height;
return;
}
if (is_optimal_size(render_size, content_size)) {
rect->x = 0;
rect->y = 0;
@ -202,7 +211,7 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
struct sc_size render_size =
sc_sdl_get_render_output_size(screen->renderer);
compute_content_rect(render_size, screen->content_size, can_upscale,
&screen->rect);
screen->render_fit, &screen->rect);
}
// render the texture to the renderer
@ -406,6 +415,7 @@ sc_screen_init(struct sc_screen *screen,
screen->video = params->video;
screen->camera = params->camera;
screen->window_aspect_ratio_lock = params->window_aspect_ratio_lock;
screen->render_fit = params->render_fit;
screen->req.x = params->window_x;
screen->req.y = params->window_y;

View file

@ -65,6 +65,8 @@ struct sc_screen {
SDL_GLContext gl_context;
#endif
enum sc_render_fit render_fit;
struct sc_size frame_size;
struct sc_size content_size; // rotated frame_size
@ -118,6 +120,7 @@ struct sc_screen_params {
bool window_aspect_ratio_lock;
bool window_borderless;
enum sc_render_fit render_fit;
enum sc_orientation orientation;
bool mipmaps;