From daba00a8197bb2fdf0ef75ca9a695fca89e99855 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 20 Nov 2024 13:01:57 +0100 Subject: [PATCH] Dissociate virtual display size and capture size Allow capturing virtual displays at a lower resolution using -m/--max-size. In the original implementation in #5370, the virtual display size was necessarily the same as the capture size. The --max-size value was only allowed to determine the virtual display size when no explicit size was provided. Since the dpi was scaled down accordingly, it is often better to create a virtual display at the target capture size directly. However, not everything is rendered according to the virtual display DPI. For example, a page in Firefox is rendered too big on small virtual displays. Thus, it makes sense to be able create a virtual display at a given size, and capture it at a lower resolution with --max-size. This is now possible using OpenGL filters. Therefore, change the behavior of --max-size for virtual displays: - it does not impact --new-display without size argument anymore (the virtual display size is the main display size); - it is used to limit the capture size (whether an explicit size is provided or not). This new behavior is consistent with main display capture. Refs #5370 comment Refs --- app/scrcpy.1 | 3 +-- app/src/cli.c | 10 +--------- doc/video.md | 6 +++--- doc/virtual_display.md | 1 - .../com/genymobile/scrcpy/device/Size.java | 6 +++++- .../scrcpy/video/NewDisplayCapture.java | 20 +++++++++++-------- .../genymobile/scrcpy/video/VideoFilter.java | 13 ++++++++++++ 7 files changed, 35 insertions(+), 24 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 711c53c6..95d5133d 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -318,14 +318,13 @@ Disable video and audio playback on the computer (equivalent to \fB\-\-no\-video .TP \fB\-\-new\-display\fR[=[\fIwidth\fRx\fIheight\fR][/\fIdpi\fR]] -Create a new display with the specified resolution and density. If not provided, they default to the main display dimensions and DPI, and \fB\-\-max\-size\fR is considered. +Create a new display with the specified resolution and density. If not provided, they default to the main display dimensions and DPI. Examples: \-\-new\-display=1920x1080 \-\-new\-display=1920x1080/420 \-\-new\-display # main display size and density - \-\-new\-display -m1920 # scaled to fit a max size of 1920 \-\-new\-display=/240 # main display size and 240 dpi .TP diff --git a/app/src/cli.c b/app/src/cli.c index 177bf934..3f2d23cb 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -590,12 +590,11 @@ static const struct sc_option options[] = { .optional_arg = true, .text = "Create a new display with the specified resolution and " "density. If not provided, they default to the main display " - "dimensions and DPI, and --max-size is considered.\n" + "dimensions and DPI.\n" "Examples:\n" " --new-display=1920x1080\n" " --new-display=1920x1080/420 # force 420 dpi\n" " --new-display # main display size and density\n" - " --new-display -m1920 # scaled to fit a max size of 1920\n" " --new-display=/240 # main display size and 240 dpi", }, { @@ -2891,13 +2890,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], LOGE("--new-display is incompatible with --no-video"); return false; } - - if (opts->max_size && opts->new_display[0] != '\0' - && opts->new_display[0] != '/') { - // An explicit size is defined (not "" nor "/") - LOGE("Cannot specify both --new-display size and -m/--max-size"); - return false; - } } if (otg) { diff --git a/doc/video.md b/doc/video.md index 63b6078c..db9571f7 100644 --- a/doc/video.md +++ b/doc/video.md @@ -193,9 +193,9 @@ phone, landscape for a tablet). Cropping is performed before `--capture-orientation` and `--angle`. -For screen mirroring, `--max-size` is applied after cropping. For camera and -virtual display mirroring, `--max-size` is applied first (because it selects the -source size rather than resizing it). +For display mirroring, `--max-size` is applied after cropping. For camera, +`--max-size` is applied first (because it selects the source size rather than +resizing the content). ## Display diff --git a/doc/virtual_display.md b/doc/virtual_display.md index 97ac01b2..7523c118 100644 --- a/doc/virtual_display.md +++ b/doc/virtual_display.md @@ -8,7 +8,6 @@ To mirror a new virtual display instead of the device screen: scrcpy --new-display=1920x1080 scrcpy --new-display=1920x1080/420 # force 420 dpi scrcpy --new-display # use the main display size and density -scrcpy --new-display -m1920 # ... scaled to fit a max size of 1920 scrcpy --new-display=/240 # use the main display size and 240 dpi ``` diff --git a/server/src/main/java/com/genymobile/scrcpy/device/Size.java b/server/src/main/java/com/genymobile/scrcpy/device/Size.java index 6500b74e..b448273d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/device/Size.java +++ b/server/src/main/java/com/genymobile/scrcpy/device/Size.java @@ -60,7 +60,7 @@ public final class Size { * @return The current size rounded. */ public Size round8() { - if ((width & 7) == 0 && (height & 7) == 0) { + if (isMultipleOf8()) { // Already a multiple of 8 return this; } @@ -80,6 +80,10 @@ public final class Size { return new Size(w, h); } + public boolean isMultipleOf8() { + return (width & 7) == 0 && (height & 7) == 0; + } + public Rect toRect() { return new Rect(0, 0, width, height); } diff --git a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java index dc9c8897..d92141af 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java @@ -48,7 +48,7 @@ public class NewDisplayCapture extends SurfaceCapture { private Size mainDisplaySize; private int mainDisplayDpi; - private int maxSize; // only used if newDisplay.getSize() != null + private int maxSize; private final Rect crop; private final boolean captureOrientationLocked; private final Orientation captureOrientation; @@ -101,7 +101,7 @@ public class NewDisplayCapture extends SurfaceCapture { int displayRotation; if (virtualDisplay == null) { if (!newDisplay.hasExplicitSize()) { - displaySize = mainDisplaySize.limit(maxSize).round8(); + displaySize = mainDisplaySize; } if (!newDisplay.hasExplicitDpi()) { dpi = scaleDpi(mainDisplaySize, mainDisplayDpi, displaySize); @@ -128,10 +128,19 @@ public class NewDisplayCapture extends SurfaceCapture { filter.addOrientation(displayRotation, captureOrientationLocked, captureOrientation); filter.addAngle(angle); + Size filteredSize = filter.getOutputSize(); + if (!filteredSize.isMultipleOf8() || (maxSize != 0 && filteredSize.getMax() > maxSize)) { + if (maxSize != 0) { + filteredSize = filteredSize.limit(maxSize); + } + filteredSize = filteredSize.round8(); + filter.addResize(filteredSize); + } + eventTransform = filter.getInverseTransform(); // DisplayInfo gives the oriented size (so videoSize includes the display rotation) - videoSize = filter.getOutputSize().limit(maxSize).round8(); + videoSize = filter.getOutputSize(); // But the virtual display video always remains in the origin orientation (the video itself is not rotated, so it must rotated manually). // This additional display rotation must not be included in the input events transform (the expected coordinates are already in the @@ -231,11 +240,6 @@ public class NewDisplayCapture extends SurfaceCapture { @Override public synchronized boolean setMaxSize(int newMaxSize) { - if (newDisplay.hasExplicitSize()) { - // Cannot retry with a different size if the display size was explicitly provided - return false; - } - maxSize = newMaxSize; return true; } diff --git a/server/src/main/java/com/genymobile/scrcpy/video/VideoFilter.java b/server/src/main/java/com/genymobile/scrcpy/video/VideoFilter.java index 6bffb51a..a27915ee 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/VideoFilter.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/VideoFilter.java @@ -103,4 +103,17 @@ public class VideoFilter { double ccwAngle = -cwAngle; transform = AffineMatrix.rotate(ccwAngle).withAspectRatio(size).fromCenter().multiply(transform); } + + public void addResize(Size targetSize) { + if (size.equals(targetSize)) { + return; + } + + if (transform == null) { + // The requested scaling is performed by the viewport (by changing the output size), but the OpenGL filter must still run, even if + // resizing is not performed by the shader. So transform MUST NOT be null. + transform = AffineMatrix.IDENTITY; + } + size = targetSize; + } }