Compare commits

..

3 Commits

Author SHA1 Message Date
Romain Vimont
2e425e6605 Fix HiDPI issues on secondary screen
Reset the renderer and texture on display scaling change.

FIXME problem with texture (see FIXME in code)

Co-authored-by: Louis Kruger <louisk@gmail.com>
2020-04-24 23:50:42 +02:00
Romain Vimont
f3b7712ea2 Add a mechanism to report errors on event handling
This paves the way to workaround HiDPI issues, which may recreate the
renderer and texture (which may fail) on window event.
2020-04-24 23:35:42 +02:00
Romain Vimont
04a31ef7b9 Remove HIDPI_SUPPORT compilation flag
We never need to build without HiDPI support.
2020-04-24 23:02:58 +02:00
6 changed files with 190 additions and 138 deletions

View File

@@ -116,9 +116,6 @@ conf.set('DEFAULT_LOCK_VIDEO_ORIENTATION', '-1') # -1: unlocked
# overridden by option --bit-rate
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
# enable High DPI support
conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
# disable console on Windows
conf.set('WINDOWS_NOCONSOLE', get_option('windows_noconsole'))

View File

@@ -7,6 +7,33 @@
#include "util/lock.h"
#include "util/log.h"
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer
// coordinates (as provided in SDL mouse events)
//
// See my question:
// <https://stackoverflow.com/questions/49111054/how-to-get-mouse-position-on-mouse-wheel-event>
static void
convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
SDL_Rect viewport;
float scale_x, scale_y;
SDL_RenderGetViewport(renderer, &viewport);
SDL_RenderGetScale(renderer, &scale_x, &scale_y);
*x = (int) (*x / scale_x) - viewport.x;
*y = (int) (*y / scale_y) - viewport.y;
}
static struct point
get_mouse_point(struct screen *screen) {
int x;
int y;
SDL_GetMouseState(&x, &y);
convert_to_renderer_coordinates(screen->renderer, &x, &y);
return (struct point) {
.x = x,
.y = y,
};
}
static const int ACTION_DOWN = 1;
static const int ACTION_UP = 1 << 1;
@@ -460,17 +487,11 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = screen->frame_size;
int ww;
int wh;
SDL_GL_GetDrawableSize(screen->window, &ww, &wh);
// SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = from->x * ww;
int32_t y = from->y * wh;
float x = from->x * screen->content_size.width;
float y = from->y * screen->content_size.height;
to->inject_touch_event.position.point =
screen_convert_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0;
return true;
@@ -487,6 +508,13 @@ input_manager_process_touch(struct input_manager *im,
}
}
static bool
is_outside_device_screen(struct input_manager *im, int x, int y)
{
return x < 0 || x >= im->screen->content_size.width ||
y < 0 || y >= im->screen->content_size.height;
}
static bool
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
struct control_msg *to) {
@@ -524,14 +552,10 @@ input_manager_process_mouse_button(struct input_manager *im,
action_home(im->controller, ACTION_DOWN | ACTION_UP);
return;
}
// double-click on black borders resize to fit the device screen
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
int x = event->x;
int y = event->y;
SDL_Rect *r = &im->screen->rect;
bool outside = x < r->x || x >= r->x + r->w
|| y < r->y || y >= r->y + r->h;
bool outside =
is_outside_device_screen(im, event->x, event->y);
if (outside) {
screen_resize_to_fit(im->screen);
return;
@@ -555,15 +579,9 @@ input_manager_process_mouse_button(struct input_manager *im,
static bool
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
struct control_msg *to) {
// mouse_x and mouse_y are expressed in pixels relative to the window
int mouse_x;
int mouse_y;
SDL_GetMouseState(&mouse_x, &mouse_y);
struct position position = {
.screen_size = screen->frame_size,
.point = screen_convert_to_frame_coords(screen, mouse_x, mouse_y),
.point = get_mouse_point(screen),
};
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;

View File

@@ -128,6 +128,7 @@ enum event_result {
EVENT_RESULT_CONTINUE,
EVENT_RESULT_STOPPED_BY_USER,
EVENT_RESULT_STOPPED_BY_EOS,
EVENT_RESULT_STOPPED_BY_ERROR,
};
static enum event_result
@@ -150,7 +151,9 @@ handle_event(SDL_Event *event, bool control) {
}
break;
case SDL_WINDOWEVENT:
screen_handle_window_event(&screen, &event->window);
if (!screen_handle_window_event(&screen, &event->window)) {
return EVENT_RESULT_STOPPED_BY_ERROR;
}
break;
case SDL_TEXTINPUT:
if (!control) {
@@ -222,6 +225,9 @@ event_loop(bool display, bool control) {
case EVENT_RESULT_STOPPED_BY_EOS:
LOGW("Device disconnected");
return false;
case EVENT_RESULT_STOPPED_BY_ERROR:
LOGC("Stopping due to unrecoverable error");
return false;
case EVENT_RESULT_CONTINUE:
break;
}

View File

@@ -87,16 +87,6 @@ get_preferred_display_bounds(struct size *bounds) {
return true;
}
static bool
is_optimal_size(struct size current_size, struct size content_size) {
// The size is optimal if we can recompute one dimension of the current
// size from the other
return current_size.height == current_size.width * content_size.height
/ content_size.width
|| current_size.width == current_size.height * content_size.width
/ content_size.height;
}
// return the optimal size of the window, with the following constraints:
// - it attempts to keep at least one dimension of the current_size (i.e. it
// crops the black borders)
@@ -109,36 +99,40 @@ get_optimal_size(struct size current_size, struct size content_size) {
return current_size;
}
struct size window_size;
struct size display_size;
// 32 bits because we need to multiply two 16 bits values
uint32_t w;
uint32_t h;
if (!get_preferred_display_bounds(&display_size)) {
// could not get display bounds, do not constraint the size
window_size.width = current_size.width;
window_size.height = current_size.height;
w = current_size.width;
h = current_size.height;
} else {
window_size.width = MIN(current_size.width, display_size.width);
window_size.height = MIN(current_size.height, display_size.height);
w = MIN(current_size.width, display_size.width);
h = MIN(current_size.height, display_size.height);
}
if (is_optimal_size(window_size, content_size)) {
return window_size;
if (h == w * content_size.height / content_size.width
|| w == h * content_size.width / content_size.height) {
// The size is already optimal, if we ignore rounding errors due to
// integer window dimensions
return (struct size) {w, h};
}
bool keep_width = content_size.width * window_size.height
> content_size.height * window_size.width;
bool keep_width = content_size.width * h > content_size.height * w;
if (keep_width) {
// remove black borders on top and bottom
window_size.height = content_size.height * window_size.width
/ content_size.width;
h = content_size.height * w / content_size.width;
} else {
// remove black borders on left and right (or none at all if it already
// fits)
window_size.width = content_size.width * window_size.height
/ content_size.height;
w = content_size.width * h / content_size.height;
}
return window_size;
// w and h must fit into 16 bits
assert(w < 0x10000 && h < 0x10000);
return (struct size) {w, h};
}
// same as get_optimal_size(), but read the current size from the window
@@ -175,43 +169,6 @@ get_initial_optimal_size(struct size content_size, uint16_t req_width,
return window_size;
}
static void
update_content_rect(struct screen *screen) {
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
struct size content_size = screen->content_size;
// The drawable size is the window size * the HiDPI scale
struct size drawable_size = {dw, dh};
SDL_Rect *rect = &screen->rect;
if (is_optimal_size(drawable_size, content_size)) {
rect->x = 0;
rect->y = 0;
rect->w = drawable_size.width;
rect->h = drawable_size.height;
return;
}
bool keep_width = content_size.width * drawable_size.height
> content_size.height * drawable_size.width;
if (keep_width) {
rect->x = 0;
rect->w = drawable_size.width;
rect->h = drawable_size.width * content_size.height
/ content_size.width;
rect->y = (drawable_size.height - rect->h) / 2;
} else {
rect->y = 0;
rect->h = drawable_size.height;
rect->w = drawable_size.height * content_size.width
/ content_size.height;
rect->x = (drawable_size.width - rect->w) / 2;
}
}
void
screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER;
@@ -260,10 +217,9 @@ screen_init_rendering(struct screen *screen, const char *window_title,
struct size window_size =
get_initial_optimal_size(content_size, window_width, window_height);
uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
#ifdef HIDPI_SUPPORT
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
#endif
uint32_t window_flags = SDL_WINDOW_HIDDEN
| SDL_WINDOW_RESIZABLE
| SDL_WINDOW_ALLOW_HIGHDPI;
if (always_on_top) {
#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
@@ -301,6 +257,13 @@ screen_init_rendering(struct screen *screen, const char *window_title,
const char *renderer_name = r ? NULL : renderer_info.name;
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
if (SDL_RenderSetLogicalSize(screen->renderer, content_size.width,
content_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
screen_destroy(screen);
return false;
}
// starts with "opengl"
screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (screen->use_opengl) {
@@ -344,10 +307,13 @@ screen_init_rendering(struct screen *screen, const char *window_title,
return false;
}
update_content_rect(screen);
screen->windowed_window_size = window_size;
struct scale_ratio *rw = &screen->scale.w;
struct scale_ratio *rh = &screen->scale.h;
SDL_GetWindowSize(screen->window, &rw->window, &rh->window);
SDL_GL_GetDrawableSize(screen->window, &rw->drawable, &rh->drawable);
return true;
}
@@ -380,6 +346,13 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
struct size new_content_size =
get_rotated_size(screen->frame_size, rotation);
if (SDL_RenderSetLogicalSize(screen->renderer,
new_content_size.width,
new_content_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
return;
}
struct size windowed_size = get_windowed_window_size(screen);
struct size target_size = {
.width = (uint32_t) windowed_size.width * new_content_size.width
@@ -394,7 +367,6 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
screen->rotation = rotation;
LOGI("Display rotation set to %u", rotation);
update_content_rect(screen);
screen_render(screen);
}
@@ -403,11 +375,18 @@ static 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) {
struct size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
if (SDL_RenderSetLogicalSize(screen->renderer,
new_content_size.width,
new_content_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
return false;
}
// frame dimension changed, destroy texture
SDL_DestroyTexture(screen->texture);
struct size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
struct size content_size = screen->content_size;
struct size windowed_size = get_windowed_window_size(screen);
struct size target_size = {
@@ -421,7 +400,6 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) {
screen->frame_size = new_frame_size;
screen->content_size = new_content_size;
update_content_rect(screen);
LOGI("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height);
@@ -471,7 +449,7 @@ void
screen_render(struct screen *screen) {
SDL_RenderClear(screen->renderer);
if (screen->rotation == 0) {
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
} else {
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
// counterclockwise (to be consistent with --lock-video-orientation)
@@ -481,14 +459,12 @@ screen_render(struct screen *screen) {
SDL_Rect *dstrect = NULL;
SDL_Rect rect;
if (screen->rotation & 1) {
rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2;
rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2;
rect.w = screen->rect.h;
rect.h = screen->rect.w;
struct size size = screen->content_size;
rect.x = (size.width - size.height) / 2;
rect.y = (size.height - size.width) / 2;
rect.w = size.height;
rect.h = size.width;
dstrect = &rect;
} else {
assert(screen->rotation == 2);
dstrect = &screen->rect;
}
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
@@ -509,7 +485,6 @@ screen_switch_fullscreen(struct screen *screen) {
apply_windowed_size(screen);
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
update_content_rect(screen);
screen_render(screen);
}
@@ -548,12 +523,74 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
content_size.height);
}
void
bool
screen_fix_hidpi(struct screen *screen) {
// If the scale ratio has changed, recreate the renderer to fix HiDPI issues
// <https://github.com/Genymobile/scrcpy/issues/15>
int ww, wh, dw, dh;
SDL_GetWindowSize(screen->window, &ww, &wh);
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
struct scale_ratio *rw = &screen->scale.w;
struct scale_ratio *rh = &screen->scale.h;
if (ww * rw->drawable == dw * rw->window &&
wh * rh->drawable == dh * rh->window) {
// same ratio, both horizontally and vertically, nothing to do
return true;
}
// update the current scale
rw->window = ww;
rh->window = wh;
rw->drawable = dw;
rh->drawable = dh;
if (screen->texture) {
SDL_DestroyTexture(screen->texture);
}
if (screen->renderer) {
SDL_DestroyRenderer(screen->renderer);
}
screen->renderer = SDL_CreateRenderer(screen->window, -1,
SDL_RENDERER_ACCELERATED);
if (!screen->renderer) {
LOGC("Could not create renderer: %s", SDL_GetError());
return false;
}
struct size content_size = screen->content_size;
if (SDL_RenderSetLogicalSize(screen->renderer, content_size.width,
content_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
SDL_DestroyRenderer(screen->renderer);
return false;
}
// FIXME this is wrong, we must update the last frame to the new texture,
// but we don't have it anymore!
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
SDL_DestroyRenderer(screen->renderer);
return false;
}
LOGD("Renderer and texture reset");
return true;
}
bool
screen_handle_window_event(struct screen *screen,
const SDL_WindowEvent *event) {
switch (event->event) {
case SDL_WINDOWEVENT_EXPOSED:
update_content_rect(screen);
if (!screen_fix_hidpi(screen)) {
// unrecoverable
return false;
}
screen_render(screen);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
@@ -569,7 +606,6 @@ screen_handle_window_event(struct screen *screen,
// window is maximized or fullscreen is enabled.
screen->windowed_window_size = get_window_size(screen->window);
}
update_content_rect(screen);
screen_render(screen);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
@@ -590,6 +626,8 @@ screen_handle_window_event(struct screen *screen,
apply_windowed_size(screen);
break;
}
return true;
}
struct point
@@ -597,27 +635,8 @@ screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
unsigned rotation = screen->rotation;
assert(rotation < 4);
int old_x = x;
int old_y = y;
int32_t w = screen->content_size.width;
int32_t h = screen->content_size.height;
// take the HiDPI scaling (dw/ww and dh/wh) into account
int ww, wh, dw, dh;
SDL_GetWindowSize(screen->window, &ww, &wh);
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
// scale (64 bits for intermediate multiplications)
x = (int64_t) (x - screen->rect.x) * w * dw / (screen->rect.w * ww);
y = (int64_t) (y - screen->rect.y) * h * dh / (screen->rect.h * wh);
LOGI("content=%dx%d, rect={%d, %d, %dx%d}: (%d, %d) -> (%d, %d)",
(int) w, (int) h,
screen->rect.x, screen->rect.y, screen->rect.w, screen->rect.h,
old_x, old_y, (int) x, (int) y);
// rotate
struct point result;
switch (rotation) {
case 0:

View File

@@ -13,6 +13,11 @@
struct video_buffer;
struct scale_ratio {
int window;
int drawable;
};
struct screen {
SDL_Window *window;
SDL_Renderer *renderer;
@@ -28,13 +33,16 @@ struct screen {
struct size windowed_window_size_backup;
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
unsigned rotation;
// rectangle of the content (excluding black borders)
struct SDL_Rect rect;
bool has_frame;
bool fullscreen;
bool maximized;
bool no_window;
bool mipmaps;
struct {
struct scale_ratio w;
struct scale_ratio h;
} scale;
};
#define SCREEN_INITIALIZER { \
@@ -60,17 +68,21 @@ struct screen {
.height = 0, \
}, \
.rotation = 0, \
.rect = { \
.x = 0, \
.y = 0, \
.w = 0, \
.h = 0, \
}, \
.has_frame = false, \
.fullscreen = false, \
.maximized = false, \
.no_window = false, \
.mipmaps = false, \
.scale = { \
.w = { \
.window = 0, \
.drawable = 0, \
}, \
.h = { \
.window = 0, \
.drawable = 0, \
}, \
} \
}
// initialize default values
@@ -119,7 +131,8 @@ void
screen_set_rotation(struct screen *screen, unsigned rotation);
// react to window events
void
// return true on success, false on unrecoverable error
bool
screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
// convert point from window coordinates to frame coordinates

View File

@@ -4,6 +4,5 @@ option('crossbuild_windows', type: 'boolean', value: false, description: 'Build
option('windows_noconsole', type: 'boolean', value: false, description: 'Disable console on Windows (pass -mwindows flag)')
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable')
option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')
option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached')
option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")')