Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Work around for multiple displays with respective scales #245

Closed
wants to merge 3 commits into from

Conversation

wsxyeah
Copy link

@wsxyeah wsxyeah commented Aug 26, 2018

It's just a workaround to resolve #15

What this pr did is to re-set a new logical size when the window scale factor got changed.
In consequence, the subsequent SDL events will get the coordinates what we wants,
which are consistent across all the displays even if there are different scales.

@rom1v
Copy link
Collaborator

rom1v commented Aug 28, 2018

(just to confirm I saw the PR, I will try to review soon, thank you)

Copy link
Collaborator

@rom1v rom1v left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your PR. 👍

In the past, I tested to scale down mouse events (hidpiscale), but while it fixed the problem on some Mac, it created the inverse problem on some others.

Changing the logical size is a promising idea, and may work for all cases (unfortunately, I have currently no way to test, I don't have access to the computers where I could reproduce anymore).

Based on this idea, I applied the change I suggested in comments, and propose this patch:

diff --git a/app/src/screen.c b/app/src/screen.c
index 5d7a400..1ae6c36 100644
--- a/app/src/screen.c
+++ b/app/src/screen.c
@@ -132,6 +132,17 @@ static inline struct size get_initial_optimal_size(struct size frame_size) {
     return get_optimal_size(frame_size, frame_size);
 }
 
+// apply hidpi scaling and call SDL_RenderSetLogicalSize
+static inline SDL_bool render_set_scaled_logical_size(struct screen *screen, struct size size) {
+    int w, h, dw, dh;
+    SDL_GL_GetDrawableSize(screen->window, &w, &h);
+    SDL_GetWindowSize(screen->window, &dw, &dh);
+    // use 32 bits unsigned not to lose precision (width and height fit in 16 bits)
+    int scaled_x = (Uint32) size.width * (Uint32) w / (Uint32) dw;
+    int scaled_y = (Uint32) size.height * (Uint32) h / (Uint32) dh;
+    return SDL_RenderSetLogicalSize(screen->renderer, scaled_x, scaled_y);
+}
+
 void screen_init(struct screen *screen) {
     *screen = (struct screen) SCREEN_INITIALIZER;
 }
@@ -163,7 +174,7 @@ SDL_bool screen_init_rendering(struct screen *screen, const char *device_name, s
         return SDL_FALSE;
     }
 
-    if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width, frame_size.height)) {
+    if (render_set_scaled_logical_size(screen, frame_size)) {
         LOGE("Could not set renderer logical size: %s", SDL_GetError());
         screen_destroy(screen);
         return SDL_FALSE;
@@ -208,7 +219,7 @@ void screen_destroy(struct screen *screen) {
 // recreate the texture and resize the window if the frame size has changed
 static SDL_bool prepare_for_frame(struct screen *screen, struct size new_frame_size) {
     if (screen->frame_size.width != new_frame_size.width || screen->frame_size.height != new_frame_size.height) {
-        if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width, new_frame_size.height)) {
+        if (render_set_scaled_logical_size(screen, new_frame_size)) {
             LOGE("Could not set renderer logical size: %s", SDL_GetError());
             return SDL_FALSE;
         }

Could you confirm it works for you?

app/src/screen.h Outdated
@@ -66,4 +68,6 @@ void screen_resize_to_fit(struct screen *screen);
// resize window to 1:1 (pixel-perfect)
void screen_resize_to_pixel_perfect(struct screen *screen);

void get_window_scale(SDL_Window *window, int *scale_x, int *scale_y);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scale is not necessary an integer.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get any non-integral scale display device for now 😅.
So I will just turn the variables into float, but some tests on such devices are still necessary.

Copy link
Collaborator

@rom1v rom1v Sep 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to use floating point values here (the ratio will lose precision), you can use a "rational" instead (cf here)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean use rational to denote the scale factor?
But we still have to convert it to float when performing the new logical size scale calculation.

Copy link
Collaborator

@rom1v rom1v Sep 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we still have to convert it to float when performing the new logical size scale calculation.

No, we don't: the width and height fit into 16 bits, so using 32 bits, you can get the best possible precision using only integers:
2f6db25#diff-cbb13e05de08766971505804dff53448R139

app/src/screen.c Outdated
int output_w, output_h;
SDL_GL_GetDrawableSize(window, &output_w, &output_h);
*scale_x = output_w / win_w;
*scale_y = output_h / win_h;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This loses precision if the scale is not an integer.

screen.scale_x = scale_x;
screen.scale_y = scale_y;
SDL_RenderSetLogicalSize(screen.renderer, logical_width, logical_height);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The computing should be done in screen.c.

Typically, add a set_logical_size() wrapper for this call and this one.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, but I'm stupid, you need it there so that it changes when you moves the window from/to the secondary screen (it still needs be done in screen.c though).

@rom1v
Copy link
Collaborator

rom1v commented Sep 4, 2018

Ping @AoDevBlue @stek29 #57

@esnaultdev
Copy link

I just checked on my setup and it works on both screens without the workaround 👍

@rom1v
Copy link
Collaborator

rom1v commented Sep 5, 2018

@AoDevBlue Thank you for your feedback. And does the workaround break it?

@esnaultdev
Copy link

esnaultdev commented Sep 5, 2018

This branch works on both screens (vertical without hdpi and horizontal with hdpi) with both the default build config

meson x --buildtype release --strip -Db_lto=true

and the old workaround one

meson x --buildtype release -Dhidpi_support=false

@stek29
Copy link
Contributor

stek29 commented Sep 6, 2018

Do you want me to test this on macOS?
Do I need to pass any flags to meson? Can I have a tl;dr please? :)

@rom1v
Copy link
Collaborator

rom1v commented Sep 6, 2018

@stek29 I just pinged you because you opened #57. I just wanted to know if this PR or my patch fixed it.

@jonalmeida
Copy link

jonalmeida commented Sep 13, 2018

Hi @rom1v! I took some time to test out both patches for your since there hasn't been any activity on this issue.

I've found @wsxyeah 's patch to work perfectly for me (see setup below), but unfortunately all touch events fail for your patch; I'm not able to use it on either monitor. There were a few build warnings but it seemed to have built properly with the default build instructions.

[3/31] Compiling C object 'app/app@@scrcpy@exe/src_file_handler.c.o'.
In file included from ../app/src/file_handler.c:1:
../app/src/file_handler.h:1:9: warning: 'FILE_HANDLER_H' is used as a header guard here, followed by #define of a different macro [-Wheader-guard]
#ifndef FILE_HANDLER_H
        ^~~~~~~~~~~~~~
../app/src/file_handler.h:2:9: note: 'FILE_HADNELR_H' is defined here; did you mean 'FILE_HANDLER_H'?
#define FILE_HADNELR_H
        ^~~~~~~~~~~~~~
        FILE_HANDLER_H
1 warning generated.
[23/31] Compiling C object 'app/app@@scrcpy@exe/src_scrcpy.c.o'.
In file included from ../app/src/scrcpy.c:16:
../app/src/file_handler.h:1:9: warning: 'FILE_HANDLER_H' is used as a header guard here, followed by #define of a different macro [-Wheader-guard]
#ifndef FILE_HANDLER_H
        ^~~~~~~~~~~~~~
../app/src/file_handler.h:2:9: note: 'FILE_HADNELR_H' is defined here; did you mean 'FILE_HANDLER_H'?
#define FILE_HADNELR_H
        ^~~~~~~~~~~~~~
        FILE_HANDLER_H
1 warning generated.

Split-View also seems to work, but there is some bugginess with re-sizing the view: clicking the split partition immediately takes you out of split-view instead of allowing you to alter it. Without this patch, it works fine (event though the touch offsets is still a problem).

screen shot 2018-09-13 at 10 31 27 am 2

Setup

MacBook Pro (15-inch, 2017)
MacOS 10.13.6
Built-in Display: 15.4-inch (2880 x 1800)
External Display: 24-inch (1920 x 1200)

screen shot 2018-09-13 at 10 19 19 am

Copy link
Collaborator

@rom1v rom1v left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As reported by @jonalmeida (#245 (comment)), your patch works. Good job 👍

There are still some tiny things which I would like to be changed, cf my comments/questions inline.

The most important is that the scale must be taken into account for two events:

  • the window is moved to another screen (your patch does this using exposed/size_changed events)
  • the frame size changes (cf prepare_for_frame())

@@ -83,6 +83,9 @@ static SDL_bool event_loop(void) {
switch (event.window.event) {
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
if (!screen_update_logical_size_if_needed(&screen)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which even is triggered when the window moves from one screen to another? SDL_WINDOW_EVENT_EXPOSED or SDL_WINDOWEVENT_SIZE_CHANGED?

On my computer (on Linux/XFCE), I get both events when I move the window from one screen to another if and only if I enable "put the windows side-by-side when moved to a screen border" (my own translation). However, otherwise, I get no event at all. So my question: is one of these events always triggered on switching screen (with different scales)? (it may be a good compromise if in practice it works, though).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As tested on my devices(an external display without any scale and mbp's builtin retina display), when I moved window from one screen to another, SDL_WINDOWEVENT_SIZE_CHANGED always gets triggered.
And listening SDL_WINDOWEVENT_EXPOSED event seems unnecessary.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, for rendering at least, SDL_WINDOWEVENT_EXPOSED was necessary when switching fullscreen on some platforms. I'm not sure it's necessary for detecting scaling changes.

logical_height *= scale_y / screen->scale_y;
screen->scale_x = scale_x;
screen->scale_y = scale_y;
if (SDL_RenderSetLogicalSize(screen->renderer, logical_width, logical_height)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logical size is rewritten on frame size changes (in prepare_for_frame), so it should take the scale into account here too.

get_window_scale(screen->window, &scale_x, &scale_y);
if (scale_x != screen->scale_x || scale_y != screen->scale_y) {
int logical_width, logical_height;
SDL_RenderGetLogicalSize(screen->renderer, &logical_width, &logical_height);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm nitpicking, but I'm not fan of computing the new value based on the previous scaled rounded value. It should be computed from the non-scaled value IMO.

SDL_bool screen_update_logical_size_if_needed(struct screen *screen) {
float scale_x, scale_y;
get_window_scale(screen->window, &scale_x, &scale_y);
if (scale_x != screen->scale_x || scale_y != screen->scale_y) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing float values for equality is fragile.

Since the "update" must also happen on frame size changes, maybe we could just set the logical size whenever SDL_WINDOW_EVENT_EXPOSED or SDL_WINDOWEVENT_SIZE_CHANGED (the one which works) or when the frame size changes? (without checking if the scale actually changed)

What do you think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that comparing float values for equality is fragile.

Updating logical size when the frame size changes is ok, but updating on SDL_WINDOWEVENT_SIZE_CHANGED is unnecessary.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, then we can keep the last width and height passed to SDL_RenderSetLogicalSize() (instead of the scale), and call it only when the parameters have changed.

@rom1v
Copy link
Collaborator

rom1v commented Sep 13, 2018

@jonalmeida Thank you very much 👍

I've found @wsxyeah 's patch to work perfectly for me

Great, good news 👍

but unfortunately all touch events fail for your patch

In my patch, I did not updated the logical size on screen change (#245 (comment)). Maybe that if you add this additional patch, it may work (if there are no other mistakes):

diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c
index 54a7f99..2e2e911 100644
--- a/app/src/scrcpy.c
+++ b/app/src/scrcpy.c
@@ -88,6 +88,7 @@ static SDL_bool event_loop(void) {
                 switch (event.window.event) {
                     case SDL_WINDOWEVENT_EXPOSED:
                     case SDL_WINDOWEVENT_SIZE_CHANGED:
+                        screen_update_scale(&screen);
                         screen_render(&screen);
                         break;
                 }
diff --git a/app/src/screen.c b/app/src/screen.c
index 1ae6c36..1666a78 100644
--- a/app/src/screen.c
+++ b/app/src/screen.c
@@ -313,3 +313,7 @@ void screen_resize_to_pixel_perfect(struct screen *screen) {
         LOGD("Resized to pixel-perfect");
     }
 }
+
+void screen_update_scale(struct screen *screen) {
+    render_set_scaled_logical_size(screen, screen->frame_size);
+}
diff --git a/app/src/screen.h b/app/src/screen.h
index 13103ea..67dcb29 100644
--- a/app/src/screen.h
+++ b/app/src/screen.h
@@ -66,4 +66,8 @@ void screen_resize_to_fit(struct screen *screen);
 // resize window to 1:1 (pixel-perfect)
 void screen_resize_to_pixel_perfect(struct screen *screen);
 
+// recompute the scale in case the window moved from/to another screen with a
+// different HiDPI
+void screen_update_scale(struct screen *screen);
+
 #endif

(This is frustrating not to be able to reproduce/test.)

There were a few build warnings but it seemed to have built properly with the default build instructions.

Thanks, fixed. eca99d5

Split-View also seems to work, but there is some bugginess with re-sizing the view: clicking the split partition immediately takes you out of split-view instead of allowing you to alter it.

I'm not sure to understand how the render size have an effect on the split-view behavior.

Without this patch, it works fine (event though the touch offsets is still a problem).

Arf, this is weird. 😞

@jonalmeida
Copy link

@rom1v I'll apply this last patch on your branch and give you an update on Monday.

Regarding the split-view, I'm not sure if that's something that can be solved easily without a reproducing environment for yourself. If I get a chance, I'll try to get some repro steps for you in a separate issue. Maybe someone else will have a better chance of fixing it then. 👍

@jonalmeida
Copy link

@rom1v I've tried your branch hidpiscale along with the patch you suggested, but it unfortunately still has issues. Touch events work on the macbook's screen, but when I move it to the external (low-density) monitor, they stop working.

Also, the release build was failing for lint warnings so I tested it using the debug variant instead:

➜  x git:(ae87885) ✗ ninja
[23/31] Compiling C object 'app/app@@scrcpy@exe/src_main.c.o'.
../app/src/main.c:255:5: warning: 'av_register_all' is deprecated [-Wdeprecated-declarations]
    av_register_all();
    ^
/usr/local/Cellar/ffmpeg/4.0.2/include/libavformat/avformat.h:2024:1: note: 'av_register_all' has been explicitly marked deprecated here
attribute_deprecated
^
/usr/local/Cellar/ffmpeg/4.0.2/include/libavutil/attributes.h:94:49: note: expanded from macro 'attribute_deprecated'
#    define attribute_deprecated __attribute__((deprecated))
                                                ^
1 warning generated.
[31/31] Generating scrcpy-server with a custom command.
FAILED: server/scrcpy-server.jar
/Users/jalmeida/src/scrcpy/server/./scripts/build-wrapper.sh ../server/. server/scrcpy-server.jar release
:server:preBuild UP-TO-DATE
:server:preReleaseBuild UP-TO-DATE
:server:compileReleaseAidl UP-TO-DATE
:server:compileReleaseRenderscript UP-TO-DATE
:server:checkReleaseManifest UP-TO-DATE
:server:generateReleaseBuildConfig UP-TO-DATE
:server:prepareLintJar UP-TO-DATE
:server:generateReleaseResValues UP-TO-DATE
:server:generateReleaseResources UP-TO-DATE
:server:mergeReleaseResources UP-TO-DATE
:server:createReleaseCompatibleScreenManifests UP-TO-DATE
:server:processReleaseManifest UP-TO-DATE
:server:splitsDiscoveryTaskRelease UP-TO-DATE
:server:processReleaseResources UP-TO-DATE
:server:generateReleaseSources UP-TO-DATE
:server:javaPreCompileRelease UP-TO-DATE
:server:compileReleaseJavaWithJavac UP-TO-DATE
:server:compileReleaseNdk NO-SOURCE
:server:compileReleaseSources UP-TO-DATE
:server:lintVitalRelease FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':server:lintVitalRelease'.
> java.lang.IllegalStateException: Expected a name but was STRING at line 1 column 99 path $[0].apkInfo.versionName

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

* Get more help at https://help.gradle.org

BUILD FAILED in 0s
15 actionable tasks: 1 executed, 14 up-to-date
ninja: build stopped: subcommand failed.

I also tested out your other branch logicalscale but for that, touch events weren't working for both screens. 😞

Please ping me if there's anything else you'd like me to test out - I'd be happy to help!

@rom1v
Copy link
Collaborator

rom1v commented Sep 18, 2018

Touch events work on the macbook's screen, but when I move it to the external (low-density) monitor, they stop working.

OK, thank you for your feedbacks.

Instead of "stop working", I guess their coordinates are scaled. For instance, if you click at (100,200), it will generate a click at (200, 400) (or the reverse). (0,0) is at top-left. See gif in #15.

../app/src/main.c:255:5: warning: 'av_register_all' is deprecated [-Wdeprecated-declarations]
    av_register_all();
    ^
/usr/local/Cellar/ffmpeg/4.0.2/include/libavformat/avformat.h:2024:1: note: 'av_register_all' has been explicitly marked deprecated here
attribute_deprecated
^
/usr/local/Cellar/ffmpeg/4.0.2/include/libavutil/attributes.h:94:49: note: expanded from macro 'attribute_deprecated'
#    define attribute_deprecated __attribute__((deprecated))
                                               ^
1 warning generated.

This warning is supposed to be fixed by fca806e. I'm interested in your value of LIBAVCODEC_VERSION_INT.

* What went wrong:
Execution failed for task ':server:lintVitalRelease'.
> java.lang.IllegalStateException: Expected a name but was STRING at line 1 column 99 path $[0].apkInfo.versionName

I cannot reproduce :(

I also tested out your other branch logicalscale but for that, touch events weren't working for both screens.

That's weird, because logicalscale is dev + my 2 patches you tested above. The working events probably depend on other factors then (the screen where you start, whether you resize the window or not, etc.).

Also, when you say "touch events", you mean "click events", right? Touch events may not work as expected (#22).

Please ping me if there's anything else you'd like me to test out - I'd be happy to help!

Thank you 😉

@jonalmeida
Copy link

jonalmeida commented Sep 18, 2018

Instead of "stop working", I guess their coordinates are scaled. For instance, if you click at (100,200), it will generate a click at (200, 400) (or the reverse). (0,0) is at top-left. See gif in #15.

Sorry about not being clear. By stopped working, I meant the scaling issue still existed.

Also, when you say "touch events", you mean "click events", right? Touch events may not work as expected (#22).

Correct, click events.

The working events probably depend on other factors then (the screen where you start, whether you resize the window or not, etc.).

You're right! I closed my laptop screen, and then opened scrcpy on the external monitor where it works as expected.

Re-opening the laptop screen put the scrcpy window back on the laptop screen and also confirms the actual clickable surface area as you can see in the screenshot below.

screen shot 2018-09-18 at 2 27 02 pm

EDIT: Interestingly, after the image above happened, I moved the scrcpy window back to the external monitor and now it works on both! I'm guessing the issue is scaling up rather than scaling down?

@rom1v
Copy link
Collaborator

rom1v commented Sep 18, 2018

Interesting.

You're right! I closed my laptop screen, and then opened scrcpy on the external monitor where it works as expected.

Which branch?

@jonalmeida
Copy link

Which branch?

origin/hidpiscale with both the two patches from the PR comments applied.

Ideally, it would be nice to have a branch that has everything applied so that I can verify that I've got the right setup.

@rom1v
Copy link
Collaborator

rom1v commented Sep 18, 2018

origin/hidpiscale with both the two patches from the PR comments applied.

OK, and this PR, it works in every cases?

@jonalmeida
Copy link

OK, and this PR, it works in every cases?

Correct.

@halxinate
Copy link

I'd be happy to help with testing, but have no means of building on my side. Is there a way to publish betas for brew?

@rom1v
Copy link
Collaborator

rom1v commented Nov 11, 2018

Does SDL 2.0.9 fix the issue on MacOS?

There is a commit from Oct 12th: metal: Fix high dpi and resizing on macOS, and clean up iOS code. Fixes bug #4250..

@Qormix
Copy link

Qormix commented Dec 18, 2018

I don't believe it does.
I just built and ran master with SDL 2.0.9 installed and the issue is still occurring.

@fleytman
Copy link

Really looking forward to the fix. When I test the application, I feel uncomfortable that scrcpy occupies the working space of the laptop, and not the second monitor.

Copy link
Collaborator

@rom1v rom1v left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(oops, wrong PR, and a review comment cannot be deleted)

@rom1v
Copy link
Collaborator

rom1v commented Oct 31, 2019

Hi,

This PR has been left aside (partly my fault) and has conflicts with current master.

A new PR attempts to fix the issue (#848). But I plan to avoid using the "logical size" at all, to compute correct mouse coordinates directly (if it works). For now, I have a branch, which works without hidpi, but still have problems with hidpi on secondary monitor: see #848 (comment).

@wsxyeah @jonalmeida your help is welcome :)

Thank you

@rom1v rom1v closed this Oct 31, 2019
@rom1v
Copy link
Collaborator

rom1v commented May 18, 2020

Please check #1408, it should fix the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Touch input offset on secondary monitor
8 participants