2018-02-08 18:14:13 +08:00
|
|
|
#include "screen.h"
|
|
|
|
|
2019-11-28 04:11:40 +08:00
|
|
|
#include <assert.h>
|
2018-02-08 18:14:13 +08:00
|
|
|
#include <string.h>
|
2019-03-03 06:52:22 +08:00
|
|
|
#include <SDL2/SDL.h>
|
2018-02-08 18:14:13 +08:00
|
|
|
|
2021-02-16 01:44:53 +08:00
|
|
|
#include "events.h"
|
2018-02-08 18:14:13 +08:00
|
|
|
#include "icon.xpm"
|
2020-06-20 04:04:06 +08:00
|
|
|
#include "scrcpy.h"
|
2018-08-15 23:01:54 +08:00
|
|
|
#include "tiny_xpm.h"
|
2019-03-02 22:16:55 +08:00
|
|
|
#include "video_buffer.h"
|
2019-11-24 18:53:00 +08:00
|
|
|
#include "util/log.h"
|
2018-02-08 18:14:13 +08:00
|
|
|
|
|
|
|
#define DISPLAY_MARGINS 96
|
|
|
|
|
2020-04-08 05:03:23 +08:00
|
|
|
static inline struct size
|
|
|
|
get_rotated_size(struct size size, int rotation) {
|
|
|
|
struct size rotated_size;
|
|
|
|
if (rotation & 1) {
|
|
|
|
rotated_size.width = size.height;
|
|
|
|
rotated_size.height = size.width;
|
|
|
|
} else {
|
|
|
|
rotated_size.width = size.width;
|
|
|
|
rotated_size.height = size.height;
|
|
|
|
}
|
|
|
|
return rotated_size;
|
|
|
|
}
|
|
|
|
|
2018-02-08 18:14:13 +08:00
|
|
|
// get the window size in a struct size
|
2019-03-03 03:09:56 +08:00
|
|
|
static struct size
|
2020-05-11 09:11:13 +08:00
|
|
|
get_window_size(const struct screen *screen) {
|
2018-02-08 18:14:13 +08:00
|
|
|
int width;
|
|
|
|
int height;
|
2020-05-11 09:11:13 +08:00
|
|
|
SDL_GetWindowSize(screen->window, &width, &height);
|
2018-02-08 18:14:13 +08:00
|
|
|
|
|
|
|
struct size size;
|
|
|
|
size.width = width;
|
|
|
|
size.height = height;
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
// set the window size to be applied when fullscreen is disabled
|
2019-03-03 03:09:56 +08:00
|
|
|
static void
|
|
|
|
set_window_size(struct screen *screen, struct size new_size) {
|
2020-05-11 09:11:13 +08:00
|
|
|
assert(!screen->fullscreen);
|
|
|
|
assert(!screen->maximized);
|
|
|
|
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
2019-03-03 06:52:22 +08:00
|
|
|
static bool
|
2019-03-03 03:09:56 +08:00
|
|
|
get_preferred_display_bounds(struct size *bounds) {
|
2018-02-08 18:14:13 +08:00
|
|
|
SDL_Rect rect;
|
2019-02-16 22:04:32 +08:00
|
|
|
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
|
2018-02-08 18:14:13 +08:00
|
|
|
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
|
|
|
|
#else
|
|
|
|
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayBounds((i), (r))
|
|
|
|
#endif
|
|
|
|
if (GET_DISPLAY_BOUNDS(0, &rect)) {
|
2018-02-13 17:10:18 +08:00
|
|
|
LOGW("Could not get display usable bounds: %s", SDL_GetError());
|
2019-03-03 06:52:22 +08:00
|
|
|
return false;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bounds->width = MAX(0, rect.w - DISPLAY_MARGINS);
|
|
|
|
bounds->height = MAX(0, rect.h - DISPLAY_MARGINS);
|
2019-03-03 06:52:22 +08:00
|
|
|
return true;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2020-04-25 06:19:44 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-02-08 18:14:13 +08:00
|
|
|
// return the optimal size of the window, with the following constraints:
|
2019-03-03 03:09:56 +08:00
|
|
|
// - it attempts to keep at least one dimension of the current_size (i.e. it
|
|
|
|
// crops the black borders)
|
2018-02-08 18:14:13 +08:00
|
|
|
// - it keeps the aspect ratio
|
|
|
|
// - it scales down to make it fit in the display_size
|
2019-03-03 03:09:56 +08:00
|
|
|
static struct size
|
2020-04-08 05:03:23 +08:00
|
|
|
get_optimal_size(struct size current_size, struct size content_size) {
|
|
|
|
if (content_size.width == 0 || content_size.height == 0) {
|
2018-02-16 06:50:38 +08:00
|
|
|
// avoid division by 0
|
|
|
|
return current_size;
|
|
|
|
}
|
|
|
|
|
2020-04-25 06:19:44 +08:00
|
|
|
struct size window_size;
|
2018-02-08 18:14:13 +08:00
|
|
|
|
2020-04-25 06:19:44 +08:00
|
|
|
struct size display_size;
|
2018-02-08 18:14:13 +08:00
|
|
|
if (!get_preferred_display_bounds(&display_size)) {
|
2019-06-24 02:49:38 +08:00
|
|
|
// could not get display bounds, do not constraint the size
|
2020-04-25 06:19:44 +08:00
|
|
|
window_size.width = current_size.width;
|
|
|
|
window_size.height = current_size.height;
|
2018-02-08 18:14:13 +08:00
|
|
|
} else {
|
2020-04-25 06:19:44 +08:00
|
|
|
window_size.width = MIN(current_size.width, display_size.width);
|
|
|
|
window_size.height = MIN(current_size.height, display_size.height);
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2020-04-25 06:19:44 +08:00
|
|
|
if (is_optimal_size(window_size, content_size)) {
|
|
|
|
return window_size;
|
2020-04-25 04:52:02 +08:00
|
|
|
}
|
|
|
|
|
2020-04-25 06:19:44 +08:00
|
|
|
bool keep_width = content_size.width * window_size.height
|
|
|
|
> content_size.height * window_size.width;
|
2018-02-08 18:14:13 +08:00
|
|
|
if (keep_width) {
|
|
|
|
// remove black borders on top and bottom
|
2020-04-25 06:19:44 +08:00
|
|
|
window_size.height = content_size.height * window_size.width
|
|
|
|
/ content_size.width;
|
2018-02-08 18:14:13 +08:00
|
|
|
} else {
|
2019-03-03 03:09:56 +08:00
|
|
|
// remove black borders on left and right (or none at all if it already
|
|
|
|
// fits)
|
2020-04-25 06:19:44 +08:00
|
|
|
window_size.width = content_size.width * window_size.height
|
|
|
|
/ content_size.height;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2020-04-25 06:19:44 +08:00
|
|
|
return window_size;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// same as get_optimal_size(), but read the current size from the window
|
2019-03-03 03:09:56 +08:00
|
|
|
static inline struct size
|
2020-04-08 05:03:23 +08:00
|
|
|
get_optimal_window_size(const struct screen *screen, struct size content_size) {
|
2020-05-11 09:11:13 +08:00
|
|
|
struct size window_size = get_window_size(screen);
|
|
|
|
return get_optimal_size(window_size, content_size);
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// initially, there is no current size, so use the frame size as current size
|
2019-11-04 01:00:11 +08:00
|
|
|
// req_width and req_height, if not 0, are the sizes requested by the user
|
2019-03-03 03:09:56 +08:00
|
|
|
static inline struct size
|
2020-04-08 05:03:23 +08:00
|
|
|
get_initial_optimal_size(struct size content_size, uint16_t req_width,
|
2019-11-04 01:00:11 +08:00
|
|
|
uint16_t req_height) {
|
|
|
|
struct size window_size;
|
|
|
|
if (!req_width && !req_height) {
|
2020-04-08 05:03:23 +08:00
|
|
|
window_size = get_optimal_size(content_size, content_size);
|
2019-11-04 01:00:11 +08:00
|
|
|
} else {
|
|
|
|
if (req_width) {
|
|
|
|
window_size.width = req_width;
|
|
|
|
} else {
|
|
|
|
// compute from the requested height
|
2020-04-08 05:03:23 +08:00
|
|
|
window_size.width = (uint32_t) req_height * content_size.width
|
|
|
|
/ content_size.height;
|
2019-11-04 01:00:11 +08:00
|
|
|
}
|
|
|
|
if (req_height) {
|
|
|
|
window_size.height = req_height;
|
|
|
|
} else {
|
|
|
|
// compute from the requested width
|
2020-04-08 05:03:23 +08:00
|
|
|
window_size.height = (uint32_t) req_width * content_size.height
|
|
|
|
/ content_size.width;
|
2019-11-04 01:00:11 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return window_size;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2020-04-18 00:44:24 +08:00
|
|
|
static void
|
|
|
|
screen_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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-20 05:02:36 +08:00
|
|
|
static void
|
|
|
|
on_frame_available(struct video_buffer *vb, void *userdata) {
|
|
|
|
(void) vb;
|
|
|
|
(void) userdata;
|
|
|
|
|
|
|
|
static SDL_Event new_frame_event = {
|
|
|
|
.type = EVENT_NEW_FRAME,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Post the event on the UI thread
|
|
|
|
SDL_PushEvent(&new_frame_event);
|
|
|
|
}
|
|
|
|
|
2021-02-24 02:59:43 +08:00
|
|
|
static void
|
|
|
|
on_frame_skipped(struct video_buffer *vb, void *userdata) {
|
|
|
|
(void) vb;
|
|
|
|
|
|
|
|
struct screen *screen = userdata;
|
|
|
|
fps_counter_add_skipped_frame(screen->fps_counter);
|
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
void
|
2021-02-20 03:56:09 +08:00
|
|
|
screen_init(struct screen *screen, struct video_buffer *vb,
|
|
|
|
struct fps_counter *fps_counter) {
|
2021-02-16 01:28:41 +08:00
|
|
|
screen->vb = vb;
|
2021-02-20 03:56:09 +08:00
|
|
|
screen->fps_counter = fps_counter;
|
2021-02-20 05:02:36 +08:00
|
|
|
|
2021-02-22 04:47:55 +08:00
|
|
|
screen->resize_pending = false;
|
|
|
|
screen->has_frame = false;
|
|
|
|
screen->fullscreen = false;
|
|
|
|
screen->maximized = false;
|
|
|
|
|
2021-02-20 05:02:36 +08:00
|
|
|
static const struct video_buffer_callbacks cbs = {
|
|
|
|
.on_frame_available = on_frame_available,
|
2021-02-24 02:59:43 +08:00
|
|
|
.on_frame_skipped = on_frame_skipped,
|
2021-02-20 05:02:36 +08:00
|
|
|
};
|
|
|
|
|
2021-02-24 02:59:43 +08:00
|
|
|
video_buffer_set_consumer_callbacks(vb, &cbs, screen);
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
static inline SDL_Texture *
|
2020-04-11 22:08:23 +08:00
|
|
|
create_texture(struct screen *screen) {
|
|
|
|
SDL_Renderer *renderer = screen->renderer;
|
|
|
|
struct size size = screen->frame_size;
|
|
|
|
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
|
|
|
|
SDL_TEXTUREACCESS_STREAMING,
|
|
|
|
size.width, size.height);
|
|
|
|
if (!texture) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (screen->mipmaps) {
|
|
|
|
struct sc_opengl *gl = &screen->gl;
|
|
|
|
|
|
|
|
SDL_GL_BindTexture(texture, NULL, NULL);
|
|
|
|
|
|
|
|
// Enable trilinear filtering for downscaling
|
|
|
|
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
|
|
|
GL_LINEAR_MIPMAP_LINEAR);
|
2020-05-23 20:32:16 +08:00
|
|
|
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f);
|
2020-04-11 22:08:23 +08:00
|
|
|
|
|
|
|
SDL_GL_UnbindTexture(texture);
|
|
|
|
}
|
|
|
|
|
|
|
|
return texture;
|
2018-03-27 17:01:40 +08:00
|
|
|
}
|
|
|
|
|
2019-03-03 06:52:22 +08:00
|
|
|
bool
|
2019-06-24 01:02:34 +08:00
|
|
|
screen_init_rendering(struct screen *screen, const char *window_title,
|
2019-08-29 13:25:17 +08:00
|
|
|
struct size frame_size, bool always_on_top,
|
2019-11-04 01:00:11 +08:00
|
|
|
int16_t window_x, int16_t window_y, uint16_t window_width,
|
2020-04-08 16:17:12 +08:00
|
|
|
uint16_t window_height, bool window_borderless,
|
2020-04-12 05:55:29 +08:00
|
|
|
uint8_t rotation, bool mipmaps) {
|
2018-02-08 18:14:13 +08:00
|
|
|
screen->frame_size = frame_size;
|
2020-04-08 16:17:12 +08:00
|
|
|
screen->rotation = rotation;
|
|
|
|
if (rotation) {
|
|
|
|
LOGI("Initial display rotation set to %u", rotation);
|
|
|
|
}
|
2020-04-08 20:11:23 +08:00
|
|
|
struct size content_size = get_rotated_size(frame_size, screen->rotation);
|
|
|
|
screen->content_size = content_size;
|
2018-02-08 18:14:13 +08:00
|
|
|
|
2019-11-04 01:00:11 +08:00
|
|
|
struct size window_size =
|
2020-04-08 05:03:23 +08:00
|
|
|
get_initial_optimal_size(content_size, window_width, window_height);
|
2019-03-03 06:52:22 +08:00
|
|
|
uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
|
2018-03-07 17:53:39 +08:00
|
|
|
#ifdef HIDPI_SUPPORT
|
|
|
|
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
|
|
|
|
#endif
|
2019-01-27 19:04:22 +08:00
|
|
|
if (always_on_top) {
|
2019-02-16 22:21:14 +08:00
|
|
|
#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
|
2019-01-27 19:04:22 +08:00
|
|
|
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
2019-02-16 22:21:14 +08:00
|
|
|
#else
|
|
|
|
LOGW("The 'always on top' flag is not available "
|
|
|
|
"(compile with SDL >= 2.0.5 to enable it)");
|
|
|
|
#endif
|
2019-01-27 19:04:22 +08:00
|
|
|
}
|
2019-08-29 13:25:17 +08:00
|
|
|
if (window_borderless) {
|
|
|
|
window_flags |= SDL_WINDOW_BORDERLESS;
|
|
|
|
}
|
2019-01-27 19:04:22 +08:00
|
|
|
|
2020-06-20 04:04:06 +08:00
|
|
|
int x = window_x != SC_WINDOW_POSITION_UNDEFINED
|
2020-03-27 00:54:06 +08:00
|
|
|
? window_x : (int) SDL_WINDOWPOS_UNDEFINED;
|
2020-06-20 04:04:06 +08:00
|
|
|
int y = window_y != SC_WINDOW_POSITION_UNDEFINED
|
2020-03-27 00:54:06 +08:00
|
|
|
? window_y : (int) SDL_WINDOWPOS_UNDEFINED;
|
2019-08-29 13:25:17 +08:00
|
|
|
screen->window = SDL_CreateWindow(window_title, x, y,
|
2019-03-03 03:09:56 +08:00
|
|
|
window_size.width, window_size.height,
|
|
|
|
window_flags);
|
2018-02-08 18:14:13 +08:00
|
|
|
if (!screen->window) {
|
2018-02-13 17:10:18 +08:00
|
|
|
LOGC("Could not create window: %s", SDL_GetError());
|
2019-03-03 06:52:22 +08:00
|
|
|
return false;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
screen->renderer = SDL_CreateRenderer(screen->window, -1,
|
|
|
|
SDL_RENDERER_ACCELERATED);
|
2018-02-08 18:14:13 +08:00
|
|
|
if (!screen->renderer) {
|
2018-02-13 17:10:18 +08:00
|
|
|
LOGC("Could not create renderer: %s", SDL_GetError());
|
2021-02-13 21:40:00 +08:00
|
|
|
SDL_DestroyWindow(screen->window);
|
2019-03-03 06:52:22 +08:00
|
|
|
return false;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2020-04-11 20:34:41 +08:00
|
|
|
SDL_RendererInfo renderer_info;
|
|
|
|
int r = SDL_GetRendererInfo(screen->renderer, &renderer_info);
|
|
|
|
const char *renderer_name = r ? NULL : renderer_info.name;
|
|
|
|
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
|
|
|
|
2021-02-22 04:47:55 +08:00
|
|
|
screen->mipmaps = false;
|
|
|
|
|
2020-04-25 05:01:58 +08:00
|
|
|
// starts with "opengl"
|
2021-02-08 02:12:14 +08:00
|
|
|
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
|
|
|
if (use_opengl) {
|
2020-04-11 22:08:23 +08:00
|
|
|
struct sc_opengl *gl = &screen->gl;
|
|
|
|
sc_opengl_init(gl);
|
|
|
|
|
|
|
|
LOGI("OpenGL version: %s", gl->version);
|
|
|
|
|
2020-04-12 05:55:29 +08:00
|
|
|
if (mipmaps) {
|
|
|
|
bool supports_mipmaps =
|
|
|
|
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
|
|
|
|
2, 0 /* OpenGL ES 2.0+ */);
|
|
|
|
if (supports_mipmaps) {
|
|
|
|
LOGI("Trilinear filtering enabled");
|
|
|
|
screen->mipmaps = true;
|
|
|
|
} else {
|
|
|
|
LOGW("Trilinear filtering disabled "
|
|
|
|
"(OpenGL 3.0+ or ES 2.0+ required)");
|
|
|
|
}
|
2020-04-11 22:08:23 +08:00
|
|
|
} else {
|
2020-04-12 05:55:29 +08:00
|
|
|
LOGI("Trilinear filtering disabled");
|
2020-04-11 22:08:23 +08:00
|
|
|
}
|
2021-02-13 21:32:24 +08:00
|
|
|
} else if (mipmaps) {
|
2020-05-23 20:23:30 +08:00
|
|
|
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
2020-04-11 22:08:23 +08:00
|
|
|
}
|
|
|
|
|
2018-02-08 18:14:13 +08:00
|
|
|
SDL_Surface *icon = read_xpm(icon_xpm);
|
2019-05-24 02:58:08 +08:00
|
|
|
if (icon) {
|
|
|
|
SDL_SetWindowIcon(screen->window, icon);
|
|
|
|
SDL_FreeSurface(icon);
|
|
|
|
} else {
|
|
|
|
LOGW("Could not load icon");
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
|
|
|
|
frame_size.height);
|
2020-04-11 22:08:23 +08:00
|
|
|
screen->texture = create_texture(screen);
|
2018-02-08 18:14:13 +08:00
|
|
|
if (!screen->texture) {
|
2018-02-13 17:10:18 +08:00
|
|
|
LOGC("Could not create texture: %s", SDL_GetError());
|
2021-02-13 21:40:00 +08:00
|
|
|
SDL_DestroyRenderer(screen->renderer);
|
|
|
|
SDL_DestroyWindow(screen->window);
|
2019-03-03 06:52:22 +08:00
|
|
|
return false;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2020-05-18 23:56:22 +08:00
|
|
|
// Reset the window size to trigger a SIZE_CHANGED event, to workaround
|
|
|
|
// HiDPI issues with some SDL renderers when several displays having
|
|
|
|
// different HiDPI scaling are connected
|
|
|
|
SDL_SetWindowSize(screen->window, window_size.width, window_size.height);
|
|
|
|
|
2020-04-18 00:44:24 +08:00
|
|
|
screen_update_content_rect(screen);
|
|
|
|
|
2019-03-03 06:52:22 +08:00
|
|
|
return true;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
void
|
|
|
|
screen_show_window(struct screen *screen) {
|
Improve startup time
On startup, the client has to:
1. listen on a port
2. push and start the server to the device
3. wait for the server to connect (accept)
4. read device name and size
5. initialize SDL
6. initialize the window and renderer
7. show the window
From the execution of the app_process command to start the server on the
device, to the execution of the java main method, it takes ~800ms. As a
consequence, step 3 also takes ~800ms on the client.
Once complete, the client initializes SDL, which takes ~500ms.
These two expensive actions are executed sequentially:
HOST DEVICE
listen on port | |
push/start the server |----------------->|| app_process loads the jar
accept the connection . ^ ||
. | ||
. | WASTE ||
. | OF ||
. | TIME ||
. | ||
. | ||
. v X execution of our java main
connection accepted |<-----------------| connect to the host
init SDL || |
|| ,----------------| send frames
|| |,---------------|
|| ||,--------------|
|| |||,-------------|
|| ||||,------------|
init window/renderer | |||||,-----------|
display frames |<++++++-----------|
(many frames skipped)
The rationale for step 3 occuring before step 5 is that initializing
SDL replaces the SIGTERM handler to receive the event in the event loop,
so pressing Ctrl+C during step 5 would not work (since it blocks the
event loop).
But this is not so important; let's parallelize the SDL initialization
with the app_process execution (we'll just add a timeout to the
connection):
HOST DEVICE
listen on port | |
push/start the server |----------------->||app_process loads the jar
init SDL || ||
|| ||
|| ||
|| ||
|| ||
|| ||
accept the connection . ||
. X execution of our java main
connection accepted |<-----------------| connect to the host
init window/renderer | |
display frames |<-----------------| send frames
|<-----------------|
In addition, show the window only once the first frame is available to
avoid flickering (opening a black window for 100~200ms).
Note: the window and renderer are initialized after the connection is
accepted because they use the device information received from the
device.
2018-02-09 20:50:54 +08:00
|
|
|
SDL_ShowWindow(screen->window);
|
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
void
|
|
|
|
screen_destroy(struct screen *screen) {
|
2021-02-13 21:40:00 +08:00
|
|
|
SDL_DestroyTexture(screen->texture);
|
|
|
|
SDL_DestroyRenderer(screen->renderer);
|
|
|
|
SDL_DestroyWindow(screen->window);
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2020-05-11 08:00:46 +08:00
|
|
|
static void
|
2020-05-11 09:11:13 +08:00
|
|
|
resize_for_content(struct screen *screen, struct size old_content_size,
|
|
|
|
struct size new_content_size) {
|
|
|
|
struct size window_size = get_window_size(screen);
|
2020-05-11 08:00:46 +08:00
|
|
|
struct size target_size = {
|
2020-05-11 09:11:13 +08:00
|
|
|
.width = (uint32_t) window_size.width * new_content_size.width
|
2020-05-11 08:00:46 +08:00
|
|
|
/ old_content_size.width,
|
2020-05-11 09:11:13 +08:00
|
|
|
.height = (uint32_t) window_size.height * new_content_size.height
|
2020-05-11 08:00:46 +08:00
|
|
|
/ old_content_size.height,
|
|
|
|
};
|
|
|
|
target_size = get_optimal_size(target_size, new_content_size);
|
|
|
|
set_window_size(screen, target_size);
|
2020-05-11 09:11:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
set_content_size(struct screen *screen, struct size new_content_size) {
|
|
|
|
if (!screen->fullscreen && !screen->maximized) {
|
|
|
|
resize_for_content(screen, screen->content_size, new_content_size);
|
|
|
|
} else if (!screen->resize_pending) {
|
|
|
|
// Store the windowed size to be able to compute the optimal size once
|
|
|
|
// fullscreen and maximized are disabled
|
|
|
|
screen->windowed_content_size = screen->content_size;
|
|
|
|
screen->resize_pending = true;
|
|
|
|
}
|
|
|
|
|
2020-05-11 08:00:46 +08:00
|
|
|
screen->content_size = new_content_size;
|
|
|
|
}
|
|
|
|
|
2020-05-11 09:11:13 +08:00
|
|
|
static void
|
|
|
|
apply_pending_resize(struct screen *screen) {
|
|
|
|
assert(!screen->fullscreen);
|
|
|
|
assert(!screen->maximized);
|
|
|
|
if (screen->resize_pending) {
|
|
|
|
resize_for_content(screen, screen->windowed_content_size,
|
|
|
|
screen->content_size);
|
|
|
|
screen->resize_pending = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-08 05:03:23 +08:00
|
|
|
void
|
|
|
|
screen_set_rotation(struct screen *screen, unsigned rotation) {
|
|
|
|
assert(rotation < 4);
|
|
|
|
if (rotation == screen->rotation) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct size new_content_size =
|
|
|
|
get_rotated_size(screen->frame_size, rotation);
|
|
|
|
|
2020-05-11 08:00:46 +08:00
|
|
|
set_content_size(screen, new_content_size);
|
2020-04-18 00:44:24 +08:00
|
|
|
|
2020-04-08 05:03:23 +08:00
|
|
|
screen->rotation = rotation;
|
|
|
|
LOGI("Display rotation set to %u", rotation);
|
|
|
|
|
2020-04-18 00:44:24 +08:00
|
|
|
screen_render(screen, true);
|
2020-04-08 05:03:23 +08:00
|
|
|
}
|
|
|
|
|
2018-02-08 18:14:13 +08:00
|
|
|
// recreate the texture and resize the window if the frame size has changed
|
2019-03-03 06:52:22 +08:00
|
|
|
static bool
|
2019-03-03 03:09:56 +08:00
|
|
|
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) {
|
2018-02-08 18:14:13 +08:00
|
|
|
// frame dimension changed, destroy texture
|
|
|
|
SDL_DestroyTexture(screen->texture);
|
|
|
|
|
|
|
|
screen->frame_size = new_frame_size;
|
|
|
|
|
2020-04-18 00:44:24 +08:00
|
|
|
struct size new_content_size =
|
|
|
|
get_rotated_size(new_frame_size, screen->rotation);
|
|
|
|
set_content_size(screen, new_content_size);
|
|
|
|
|
|
|
|
screen_update_content_rect(screen);
|
|
|
|
|
2019-03-08 03:17:09 +08:00
|
|
|
LOGI("New texture: %" PRIu16 "x%" PRIu16,
|
2018-02-08 18:14:13 +08:00
|
|
|
screen->frame_size.width, screen->frame_size.height);
|
2020-04-11 22:08:23 +08:00
|
|
|
screen->texture = create_texture(screen);
|
2018-02-08 18:14:13 +08:00
|
|
|
if (!screen->texture) {
|
2018-02-13 17:10:18 +08:00
|
|
|
LOGC("Could not create texture: %s", SDL_GetError());
|
2019-03-03 06:52:22 +08:00
|
|
|
return false;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-03 06:52:22 +08:00
|
|
|
return true;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// write the frame into the texture
|
2019-03-03 03:09:56 +08:00
|
|
|
static void
|
|
|
|
update_texture(struct screen *screen, const AVFrame *frame) {
|
2018-02-08 18:14:13 +08:00
|
|
|
SDL_UpdateYUVTexture(screen->texture, NULL,
|
|
|
|
frame->data[0], frame->linesize[0],
|
|
|
|
frame->data[1], frame->linesize[1],
|
|
|
|
frame->data[2], frame->linesize[2]);
|
2020-04-11 22:08:23 +08:00
|
|
|
|
|
|
|
if (screen->mipmaps) {
|
|
|
|
SDL_GL_BindTexture(screen->texture, NULL, NULL);
|
|
|
|
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
|
|
|
|
SDL_GL_UnbindTexture(screen->texture);
|
|
|
|
}
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2021-02-16 01:44:53 +08:00
|
|
|
static bool
|
2021-02-16 01:28:41 +08:00
|
|
|
screen_update_frame(struct screen *screen) {
|
2021-02-20 04:16:57 +08:00
|
|
|
const AVFrame *frame = video_buffer_consumer_take_frame(screen->vb);
|
2021-02-20 03:56:09 +08:00
|
|
|
|
|
|
|
fps_counter_add_rendered_frame(screen->fps_counter);
|
|
|
|
|
2018-02-08 18:14:13 +08:00
|
|
|
struct size new_frame_size = {frame->width, frame->height};
|
|
|
|
if (!prepare_for_frame(screen, new_frame_size)) {
|
2019-03-03 06:52:22 +08:00
|
|
|
return false;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
update_texture(screen, frame);
|
2018-02-09 18:14:47 +08:00
|
|
|
|
2020-04-18 00:44:24 +08:00
|
|
|
screen_render(screen, false);
|
2019-03-03 06:52:22 +08:00
|
|
|
return true;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
void
|
2020-04-18 00:44:24 +08:00
|
|
|
screen_render(struct screen *screen, bool update_content_rect) {
|
|
|
|
if (update_content_rect) {
|
|
|
|
screen_update_content_rect(screen);
|
|
|
|
}
|
|
|
|
|
2018-02-08 18:14:13 +08:00
|
|
|
SDL_RenderClear(screen->renderer);
|
2020-04-08 05:03:23 +08:00
|
|
|
if (screen->rotation == 0) {
|
2020-04-18 00:44:24 +08:00
|
|
|
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
|
2020-04-08 05:03:23 +08:00
|
|
|
} else {
|
|
|
|
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
|
|
|
|
// counterclockwise (to be consistent with --lock-video-orientation)
|
|
|
|
int cw_rotation = (4 - screen->rotation) % 4;
|
|
|
|
double angle = 90 * cw_rotation;
|
|
|
|
|
|
|
|
SDL_Rect *dstrect = NULL;
|
|
|
|
SDL_Rect rect;
|
|
|
|
if (screen->rotation & 1) {
|
2020-04-18 00:44:24 +08:00
|
|
|
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;
|
2020-04-08 05:03:23 +08:00
|
|
|
dstrect = ▭
|
2020-04-18 00:44:24 +08:00
|
|
|
} else {
|
|
|
|
assert(screen->rotation == 2);
|
|
|
|
dstrect = &screen->rect;
|
2020-04-08 05:03:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
|
|
|
|
angle, NULL, 0);
|
|
|
|
}
|
2018-02-08 18:14:13 +08:00
|
|
|
SDL_RenderPresent(screen->renderer);
|
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
void
|
|
|
|
screen_switch_fullscreen(struct screen *screen) {
|
2019-03-03 06:52:22 +08:00
|
|
|
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
2018-02-08 18:14:13 +08:00
|
|
|
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
|
2018-02-13 17:10:18 +08:00
|
|
|
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
|
2018-02-08 18:14:13 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
screen->fullscreen = !screen->fullscreen;
|
2020-05-11 09:11:13 +08:00
|
|
|
if (!screen->fullscreen && !screen->maximized) {
|
|
|
|
apply_pending_resize(screen);
|
|
|
|
}
|
2018-02-08 18:14:13 +08:00
|
|
|
|
2018-02-13 17:10:18 +08:00
|
|
|
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
|
2020-04-18 00:44:24 +08:00
|
|
|
screen_render(screen, true);
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
void
|
|
|
|
screen_resize_to_fit(struct screen *screen) {
|
2020-05-16 00:53:43 +08:00
|
|
|
if (screen->fullscreen || screen->maximized) {
|
2019-11-12 04:44:27 +08:00
|
|
|
return;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
2019-11-12 04:44:27 +08:00
|
|
|
|
|
|
|
struct size optimal_size =
|
2020-04-08 20:11:23 +08:00
|
|
|
get_optimal_window_size(screen, screen->content_size);
|
2019-11-12 04:44:27 +08:00
|
|
|
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
|
2020-04-18 06:31:31 +08:00
|
|
|
LOGD("Resized to optimal size: %ux%u", optimal_size.width,
|
|
|
|
optimal_size.height);
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
void
|
|
|
|
screen_resize_to_pixel_perfect(struct screen *screen) {
|
2019-11-12 04:44:27 +08:00
|
|
|
if (screen->fullscreen) {
|
|
|
|
return;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
2019-11-12 04:44:27 +08:00
|
|
|
|
|
|
|
if (screen->maximized) {
|
|
|
|
SDL_RestoreWindow(screen->window);
|
|
|
|
screen->maximized = false;
|
|
|
|
}
|
|
|
|
|
2020-04-08 20:11:23 +08:00
|
|
|
struct size content_size = screen->content_size;
|
2020-04-08 05:03:23 +08:00
|
|
|
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
|
2020-04-18 06:31:31 +08:00
|
|
|
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
|
|
|
|
content_size.height);
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
2019-10-20 21:32:33 +08:00
|
|
|
|
2021-02-16 01:44:53 +08:00
|
|
|
bool
|
|
|
|
screen_handle_event(struct screen *screen, SDL_Event *event) {
|
|
|
|
switch (event->type) {
|
|
|
|
case EVENT_NEW_FRAME:
|
|
|
|
if (!screen->has_frame) {
|
|
|
|
screen->has_frame = true;
|
|
|
|
// this is the very first frame, show the window
|
|
|
|
screen_show_window(screen);
|
|
|
|
}
|
|
|
|
bool ok = screen_update_frame(screen);
|
|
|
|
if (!ok) {
|
|
|
|
LOGW("Frame update failed\n");
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
case SDL_WINDOWEVENT:
|
|
|
|
if (!screen->has_frame) {
|
|
|
|
// Do nothing
|
|
|
|
return true;
|
|
|
|
}
|
2021-02-16 01:47:17 +08:00
|
|
|
switch (event->window.event) {
|
|
|
|
case SDL_WINDOWEVENT_EXPOSED:
|
|
|
|
screen_render(screen, true);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
|
|
screen_render(screen, true);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_MAXIMIZED:
|
|
|
|
screen->maximized = true;
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_RESTORED:
|
|
|
|
if (screen->fullscreen) {
|
|
|
|
// On Windows, in maximized+fullscreen, disabling
|
|
|
|
// fullscreen mode unexpectedly triggers the "restored"
|
|
|
|
// then "maximized" events, leaving the window in a
|
|
|
|
// weird state (maximized according to the events, but
|
|
|
|
// not maximized visually).
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
screen->maximized = false;
|
|
|
|
apply_pending_resize(screen);
|
|
|
|
break;
|
|
|
|
}
|
2021-02-16 01:44:53 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-04-18 00:43:29 +08:00
|
|
|
struct point
|
2020-06-25 14:54:40 +08:00
|
|
|
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
|
|
|
int32_t x, int32_t y) {
|
2020-04-18 00:43:29 +08:00
|
|
|
unsigned rotation = screen->rotation;
|
|
|
|
assert(rotation < 4);
|
|
|
|
|
|
|
|
int32_t w = screen->content_size.width;
|
|
|
|
int32_t h = screen->content_size.height;
|
2020-04-18 00:44:24 +08:00
|
|
|
|
|
|
|
|
|
|
|
x = (int64_t) (x - screen->rect.x) * w / screen->rect.w;
|
|
|
|
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
|
|
|
|
|
|
|
|
// rotate
|
2020-04-18 00:43:29 +08:00
|
|
|
struct point result;
|
|
|
|
switch (rotation) {
|
|
|
|
case 0:
|
|
|
|
result.x = x;
|
|
|
|
result.y = y;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
result.x = h - y;
|
|
|
|
result.y = x;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
result.x = w - x;
|
|
|
|
result.y = h - y;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assert(rotation == 3);
|
|
|
|
result.x = y;
|
|
|
|
result.y = w - x;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2020-04-18 00:44:24 +08:00
|
|
|
|
2020-06-25 14:54:40 +08:00
|
|
|
struct point
|
|
|
|
screen_convert_window_to_frame_coords(struct screen *screen,
|
|
|
|
int32_t x, int32_t y) {
|
|
|
|
screen_hidpi_scale_coords(screen, &x, &y);
|
|
|
|
return screen_convert_drawable_to_frame_coords(screen, x, y);
|
|
|
|
}
|
|
|
|
|
2020-04-18 00:44:24 +08:00
|
|
|
void
|
|
|
|
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
|
|
|
|
// 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 for HiDPI (64 bits for intermediate multiplications)
|
|
|
|
*x = (int64_t) *x * dw / ww;
|
|
|
|
*y = (int64_t) *y * dh / wh;
|
|
|
|
}
|