mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-02-23 23:04:43 +01:00
Compare commits
16 Commits
dev
...
renderer.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2e9aa85e3 | ||
|
|
82c0ffc69b | ||
|
|
01cef27bea | ||
|
|
2f54970f60 | ||
|
|
665fce0707 | ||
|
|
cea820a057 | ||
|
|
6f29e9427e | ||
|
|
8803f452a3 | ||
|
|
0fcf332b37 | ||
|
|
0cd5c35f19 | ||
|
|
63961db125 | ||
|
|
503ff8ac79 | ||
|
|
089c62b1a2 | ||
|
|
72a2ae3224 | ||
|
|
d9669a2610 | ||
|
|
f1cb2213b2 |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
@@ -15,7 +15,6 @@ src = [
|
||||
'src/delay_buffer.c',
|
||||
'src/demuxer.c',
|
||||
'src/device_msg.c',
|
||||
'src/display.c',
|
||||
'src/events.c',
|
||||
'src/icon.c',
|
||||
'src/file_pusher.c',
|
||||
@@ -33,6 +32,7 @@ src = [
|
||||
'src/scrcpy.c',
|
||||
'src/screen.c',
|
||||
'src/server.c',
|
||||
'src/texture.c',
|
||||
'src/version.c',
|
||||
'src/hid/hid_gamepad.c',
|
||||
'src/hid/hid_keyboard.c',
|
||||
@@ -191,8 +191,7 @@ executable('scrcpy', src,
|
||||
datadir = get_option('datadir') # by default 'share'
|
||||
|
||||
install_man('scrcpy.1')
|
||||
install_data('data/icon.png',
|
||||
rename: 'scrcpy.png',
|
||||
install_data('data/scrcpy.png',
|
||||
install_dir: datadir / 'icons/hicolor/256x256/apps')
|
||||
install_data('data/zsh-completion/_scrcpy',
|
||||
install_dir: datadir / 'zsh/site-functions')
|
||||
@@ -283,6 +282,6 @@ endif
|
||||
|
||||
if meson.version().version_compare('>= 0.58.0')
|
||||
devenv = environment()
|
||||
devenv.set('SCRCPY_ICON_PATH', meson.current_source_dir() / 'data/icon.png')
|
||||
devenv.set('SCRCPY_ICON_DIR', meson.current_source_dir() / 'data')
|
||||
meson.add_devenv(devenv)
|
||||
endif
|
||||
|
||||
@@ -851,8 +851,8 @@ Path to adb.
|
||||
Device serial to use if no selector (\fB-s\fR, \fB-d\fR, \fB-e\fR or \fB\-\-tcpip=\fIaddr\fR) is specified.
|
||||
|
||||
.TP
|
||||
.B SCRCPY_ICON_PATH
|
||||
Path to the program icon.
|
||||
.B SCRCPY_ICON_DIR
|
||||
Path to the icon directory.
|
||||
|
||||
.TP
|
||||
.B SCRCPY_SERVER_PATH
|
||||
|
||||
@@ -1249,8 +1249,8 @@ static const struct sc_envvar envvars[] = {
|
||||
"--tcpip=<addr>) is specified",
|
||||
},
|
||||
{
|
||||
.name = "SCRCPY_ICON_PATH",
|
||||
.text = "Path to the program icon",
|
||||
.name = "SCRCPY_ICON_DIR",
|
||||
.text = "Path to the icon directory",
|
||||
},
|
||||
{
|
||||
.name = "SCRCPY_SERVER_PATH",
|
||||
|
||||
@@ -1,429 +0,0 @@
|
||||
#include "display.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/sdl.h"
|
||||
|
||||
static bool
|
||||
sc_display_init_novideo_icon(struct sc_display *display,
|
||||
SDL_Surface *icon_novideo) {
|
||||
assert(icon_novideo);
|
||||
|
||||
bool ok = SDL_SetRenderLogicalPresentation(display->renderer,
|
||||
icon_novideo->w,
|
||||
icon_novideo->h,
|
||||
SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
if (!ok) {
|
||||
LOGW("Could not set renderer logical size: %s", SDL_GetError());
|
||||
// don't fail
|
||||
}
|
||||
|
||||
display->texture = SDL_CreateTextureFromSurface(display->renderer,
|
||||
icon_novideo);
|
||||
if (!display->texture) {
|
||||
LOGE("Could not create texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_display_init(struct sc_display *display, SDL_Window *window,
|
||||
SDL_Surface *icon_novideo, bool mipmaps) {
|
||||
display->renderer = SDL_CreateRenderer(window, NULL);
|
||||
if (!display->renderer) {
|
||||
LOGE("Could not create renderer: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *renderer_name = SDL_GetRendererName(display->renderer);
|
||||
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
||||
|
||||
display->mipmaps = false;
|
||||
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
display->gl_context = NULL;
|
||||
#endif
|
||||
|
||||
// starts with "opengl"
|
||||
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||
if (use_opengl) {
|
||||
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
// Persuade macOS to give us something better than OpenGL 2.1.
|
||||
// If we create a Core Profile context, we get the best OpenGL version.
|
||||
bool ok = SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
|
||||
SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
if (!ok) {
|
||||
LOGW("Could not set a GL Core Profile Context");
|
||||
}
|
||||
|
||||
LOGD("Creating OpenGL Core Profile context");
|
||||
display->gl_context = SDL_GL_CreateContext(window);
|
||||
if (!display->gl_context) {
|
||||
LOGE("Could not create OpenGL context: %s", SDL_GetError());
|
||||
SDL_DestroyRenderer(display->renderer);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct sc_opengl *gl = &display->gl;
|
||||
sc_opengl_init(gl);
|
||||
|
||||
LOGI("OpenGL version: %s", gl->version);
|
||||
|
||||
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");
|
||||
display->mipmaps = true;
|
||||
} else {
|
||||
LOGW("Trilinear filtering disabled "
|
||||
"(OpenGL 3.0+ or ES 2.0+ required)");
|
||||
}
|
||||
} else {
|
||||
LOGI("Trilinear filtering disabled");
|
||||
}
|
||||
} else if (mipmaps) {
|
||||
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
||||
}
|
||||
|
||||
display->texture = NULL;
|
||||
display->pending.flags = 0;
|
||||
display->pending.frame = NULL;
|
||||
|
||||
if (icon_novideo) {
|
||||
// Without video, set a static scrcpy icon as window content
|
||||
bool ok = sc_display_init_novideo_icon(display, icon_novideo);
|
||||
if (!ok) {
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GL_DestroyContext(display->gl_context);
|
||||
#endif
|
||||
SDL_DestroyRenderer(display->renderer);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_display_destroy(struct sc_display *display) {
|
||||
if (display->pending.frame) {
|
||||
av_frame_free(&display->pending.frame);
|
||||
}
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GL_DestroyContext(display->gl_context);
|
||||
#endif
|
||||
if (display->texture) {
|
||||
SDL_DestroyTexture(display->texture);
|
||||
}
|
||||
SDL_DestroyRenderer(display->renderer);
|
||||
}
|
||||
|
||||
static enum SDL_Colorspace
|
||||
sc_display_to_sdl_color_space(enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
bool full_range = color_range == AVCOL_RANGE_JPEG;
|
||||
|
||||
switch (color_space) {
|
||||
case AVCOL_SPC_BT709:
|
||||
case AVCOL_SPC_RGB:
|
||||
return full_range ? SDL_COLORSPACE_BT709_FULL
|
||||
: SDL_COLORSPACE_BT709_LIMITED;
|
||||
case AVCOL_SPC_BT470BG:
|
||||
case AVCOL_SPC_SMPTE170M:
|
||||
return full_range ? SDL_COLORSPACE_BT601_FULL
|
||||
: SDL_COLORSPACE_BT601_LIMITED;
|
||||
case AVCOL_SPC_BT2020_NCL:
|
||||
case AVCOL_SPC_BT2020_CL:
|
||||
return full_range ? SDL_COLORSPACE_BT2020_FULL
|
||||
: SDL_COLORSPACE_BT2020_LIMITED;
|
||||
default:
|
||||
return SDL_COLORSPACE_JPEG;
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_Texture *
|
||||
sc_display_create_texture(struct sc_display *display,
|
||||
struct sc_size size, enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
SDL_PropertiesID props = SDL_CreateProperties();
|
||||
if (!props) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
enum SDL_Colorspace sdl_color_space =
|
||||
sc_display_to_sdl_color_space(color_space, color_range);
|
||||
|
||||
bool ok =
|
||||
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER,
|
||||
SDL_PIXELFORMAT_YV12);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER,
|
||||
SDL_TEXTUREACCESS_STREAMING);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER,
|
||||
size.width);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER,
|
||||
size.height);
|
||||
ok &= SDL_SetNumberProperty(props,
|
||||
SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER,
|
||||
sdl_color_space);
|
||||
|
||||
if (!ok) {
|
||||
LOGE("Could not set texture properties");
|
||||
SDL_DestroyProperties(props);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_Renderer *renderer = display->renderer;
|
||||
SDL_Texture *texture = SDL_CreateTextureWithProperties(renderer, props);
|
||||
SDL_DestroyProperties(props);
|
||||
if (!texture) {
|
||||
LOGD("Could not create texture: %s", SDL_GetError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (display->mipmaps) {
|
||||
struct sc_opengl *gl = &display->gl;
|
||||
|
||||
SDL_PropertiesID props = SDL_GetTextureProperties(texture);
|
||||
if (!props) {
|
||||
LOGE("Could not get texture properties: %s", SDL_GetError());
|
||||
SDL_DestroyTexture(texture);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *renderer_name = SDL_GetRendererName(display->renderer);
|
||||
const char *key = !renderer_name || !strcmp(renderer_name, "opengl")
|
||||
? SDL_PROP_TEXTURE_OPENGL_TEXTURE_NUMBER
|
||||
: SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_NUMBER;
|
||||
|
||||
int64_t texture_id = SDL_GetNumberProperty(props, key, 0);
|
||||
SDL_DestroyProperties(props);
|
||||
if (!texture_id) {
|
||||
LOGE("Could not get texture id: %s", SDL_GetError());
|
||||
SDL_DestroyTexture(texture);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(!(texture_id & ~0xFFFFFFFF)); // fits in uint32_t
|
||||
display->texture_id = texture_id;
|
||||
gl->BindTexture(GL_TEXTURE_2D, display->texture_id);
|
||||
|
||||
// Enable trilinear filtering for downscaling
|
||||
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
||||
GL_LINEAR_MIPMAP_LINEAR);
|
||||
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f);
|
||||
|
||||
gl->BindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
static inline void
|
||||
sc_display_set_pending_texture(struct sc_display *display,
|
||||
struct sc_size size,
|
||||
enum AVColorRange color_range) {
|
||||
assert(!display->texture);
|
||||
display->pending.texture.size = size;
|
||||
display->pending.texture.color_range = color_range;
|
||||
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_TEXTURE;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_set_pending_frame(struct sc_display *display, const AVFrame *frame) {
|
||||
if (!display->pending.frame) {
|
||||
display->pending.frame = av_frame_alloc();
|
||||
if (!display->pending.frame) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
av_frame_unref(display->pending.frame);
|
||||
int r = av_frame_ref(display->pending.frame, frame);
|
||||
if (r) {
|
||||
LOGE("Could not ref frame: %d", r);
|
||||
return false;
|
||||
}
|
||||
|
||||
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_FRAME;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Forward declaration
|
||||
static bool
|
||||
sc_display_update_texture_internal(struct sc_display *display,
|
||||
const AVFrame *frame);
|
||||
|
||||
static bool
|
||||
sc_display_apply_pending(struct sc_display *display) {
|
||||
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_TEXTURE) {
|
||||
assert(!display->texture);
|
||||
display->texture =
|
||||
sc_display_create_texture(display,
|
||||
display->pending.texture.size,
|
||||
display->pending.texture.color_space,
|
||||
display->pending.texture.color_range);
|
||||
if (!display->texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_TEXTURE;
|
||||
}
|
||||
|
||||
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_FRAME) {
|
||||
assert(display->pending.frame);
|
||||
bool ok = sc_display_update_texture_internal(display,
|
||||
display->pending.frame);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
av_frame_unref(display->pending.frame);
|
||||
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_FRAME;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_prepare_texture_internal(struct sc_display *display,
|
||||
struct sc_size size,
|
||||
enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
assert(size.width && size.height);
|
||||
|
||||
if (display->texture) {
|
||||
SDL_DestroyTexture(display->texture);
|
||||
}
|
||||
|
||||
display->texture =
|
||||
sc_display_create_texture(display, size, color_space, color_range);
|
||||
if (!display->texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGI("Texture: %" PRIu16 "x%" PRIu16, size.width, size.height);
|
||||
return true;
|
||||
}
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_prepare_texture(struct sc_display *display, struct sc_size size,
|
||||
enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
bool ok = sc_display_prepare_texture_internal(display, size, color_space,
|
||||
color_range);
|
||||
if (!ok) {
|
||||
sc_display_set_pending_texture(display, size, color_range);
|
||||
return SC_DISPLAY_RESULT_PENDING;
|
||||
|
||||
}
|
||||
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_update_texture_internal(struct sc_display *display,
|
||||
const AVFrame *frame) {
|
||||
bool ok = SDL_UpdateYUVTexture(display->texture, NULL,
|
||||
frame->data[0], frame->linesize[0],
|
||||
frame->data[1], frame->linesize[1],
|
||||
frame->data[2], frame->linesize[2]);
|
||||
if (!ok) {
|
||||
LOGD("Could not update texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (display->mipmaps) {
|
||||
assert(display->texture_id);
|
||||
struct sc_opengl *gl = &display->gl;
|
||||
|
||||
gl->BindTexture(GL_TEXTURE_2D, display->texture_id);
|
||||
gl->GenerateMipmap(GL_TEXTURE_2D);
|
||||
gl->BindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
|
||||
bool ok = sc_display_update_texture_internal(display, frame);
|
||||
if (!ok) {
|
||||
ok = sc_display_set_pending_frame(display, frame);
|
||||
if (!ok) {
|
||||
LOGE("Could not set pending frame");
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
|
||||
return SC_DISPLAY_RESULT_PENDING;
|
||||
}
|
||||
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
||||
enum sc_orientation orientation) {
|
||||
sc_sdl_render_clear(display->renderer);
|
||||
|
||||
if (display->pending.flags) {
|
||||
bool ok = sc_display_apply_pending(display);
|
||||
if (!ok) {
|
||||
return SC_DISPLAY_RESULT_PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Renderer *renderer = display->renderer;
|
||||
SDL_Texture *texture = display->texture;
|
||||
|
||||
if (orientation == SC_ORIENTATION_0) {
|
||||
SDL_FRect frect;
|
||||
SDL_FRect *fgeometry = NULL;
|
||||
if (geometry) {
|
||||
SDL_RectToFRect(geometry, &frect);
|
||||
fgeometry = &frect;
|
||||
}
|
||||
bool ok = SDL_RenderTexture(renderer, texture, NULL, fgeometry);
|
||||
if (!ok) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
} else {
|
||||
unsigned cw_rotation = sc_orientation_get_rotation(orientation);
|
||||
double angle = 90 * cw_rotation;
|
||||
|
||||
SDL_FRect frect;
|
||||
if (sc_orientation_is_swap(orientation)) {
|
||||
frect.x = geometry->x + (geometry->w - geometry->h) / 2.f;
|
||||
frect.y = geometry->y + (geometry->h - geometry->w) / 2.f;
|
||||
frect.w = geometry->h;
|
||||
frect.h = geometry->w;
|
||||
} else {
|
||||
SDL_RectToFRect(geometry, &frect);
|
||||
}
|
||||
|
||||
SDL_FlipMode flip = sc_orientation_is_mirror(orientation)
|
||||
? SDL_FLIP_HORIZONTAL : 0;
|
||||
|
||||
bool ok = SDL_RenderTextureRotated(renderer, texture, NULL, &frect,
|
||||
angle, NULL, flip);
|
||||
if (!ok) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
sc_sdl_render_present(display->renderer);
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
#ifndef SC_DISPLAY_H
|
||||
#define SC_DISPLAY_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <libavutil/frame.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "opengl.h"
|
||||
#include "options.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
#endif
|
||||
|
||||
struct sc_display {
|
||||
SDL_Renderer *renderer;
|
||||
SDL_Texture *texture;
|
||||
|
||||
struct sc_opengl gl;
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GLContext gl_context;
|
||||
#endif
|
||||
|
||||
bool mipmaps;
|
||||
uint32_t texture_id; // only set if mipmaps is enabled
|
||||
|
||||
struct {
|
||||
#define SC_DISPLAY_PENDING_FLAG_TEXTURE 1
|
||||
#define SC_DISPLAY_PENDING_FLAG_FRAME 2
|
||||
int8_t flags;
|
||||
struct {
|
||||
struct sc_size size;
|
||||
enum AVColorSpace color_space;
|
||||
enum AVColorRange color_range;
|
||||
} texture;
|
||||
AVFrame *frame;
|
||||
} pending;
|
||||
};
|
||||
|
||||
enum sc_display_result {
|
||||
SC_DISPLAY_RESULT_OK,
|
||||
SC_DISPLAY_RESULT_PENDING,
|
||||
SC_DISPLAY_RESULT_ERROR,
|
||||
};
|
||||
|
||||
bool
|
||||
sc_display_init(struct sc_display *display, SDL_Window *window,
|
||||
SDL_Surface *icon_novideo, bool mipmaps);
|
||||
|
||||
void
|
||||
sc_display_destroy(struct sc_display *display);
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_prepare_texture(struct sc_display *display, struct sc_size size,
|
||||
enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range);
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_update_texture(struct sc_display *display, const AVFrame *frame);
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
||||
enum sc_orientation orientation);
|
||||
|
||||
#endif
|
||||
@@ -14,21 +14,23 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "util/env.h"
|
||||
#ifdef PORTABLE
|
||||
# include "util/file.h"
|
||||
#endif
|
||||
#include "util/file.h"
|
||||
#include "util/log.h"
|
||||
|
||||
#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png"
|
||||
#define SCRCPY_ICON_FILENAME "scrcpy.png"
|
||||
#define SCRCPY_DEFAULT_ICON_PATH \
|
||||
PREFIX "/share/icons/hicolor/256x256/apps/scrcpy.png"
|
||||
|
||||
static char *
|
||||
get_icon_path(void) {
|
||||
char *icon_path = sc_get_env("SCRCPY_ICON_PATH");
|
||||
if (icon_path) {
|
||||
char *icon_path;
|
||||
|
||||
char *icon_dir = sc_get_env("SCRCPY_ICON_DIR");
|
||||
if (icon_dir) {
|
||||
// if the envvar is set, use it
|
||||
LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
|
||||
icon_path = sc_file_build_path(icon_dir, SCRCPY_ICON_FILENAME);
|
||||
free(icon_dir);
|
||||
LOGD("Using icon from SCRCPY_ICON_DIR: %s", icon_path);
|
||||
return icon_path;
|
||||
}
|
||||
|
||||
@@ -40,7 +42,7 @@ get_icon_path(void) {
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
|
||||
icon_path = sc_file_get_local_path(SCRCPY_ICON_FILENAME);
|
||||
if (!icon_path) {
|
||||
LOGE("Could not get icon path");
|
||||
return NULL;
|
||||
@@ -176,8 +178,8 @@ to_sdl_pixel_format(enum AVPixelFormat fmt) {
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_Surface *
|
||||
load_from_path(const char *path) {
|
||||
SDL_Surface *
|
||||
sc_icon_load(const char *path) {
|
||||
AVFrame *frame = decode_image(path);
|
||||
if (!frame) {
|
||||
return NULL;
|
||||
@@ -274,19 +276,19 @@ error:
|
||||
}
|
||||
|
||||
SDL_Surface *
|
||||
scrcpy_icon_load(void) {
|
||||
sc_icon_load_scrcpy(void) {
|
||||
char *icon_path = get_icon_path();
|
||||
if (!icon_path) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_Surface *icon = load_from_path(icon_path);
|
||||
SDL_Surface *icon = sc_icon_load(icon_path);
|
||||
free(icon_path);
|
||||
return icon;
|
||||
}
|
||||
|
||||
void
|
||||
scrcpy_icon_destroy(SDL_Surface *icon) {
|
||||
sc_icon_destroy(SDL_Surface *icon) {
|
||||
SDL_PropertiesID props = SDL_GetSurfaceProperties(icon);
|
||||
assert(props);
|
||||
AVFrame *frame = SDL_GetPointerProperty(props, "sc_frame", NULL);
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
#include <SDL3/SDL_surface.h>
|
||||
|
||||
SDL_Surface *
|
||||
scrcpy_icon_load(void);
|
||||
sc_icon_load(const char *path);
|
||||
|
||||
SDL_Surface *
|
||||
sc_icon_load_scrcpy(void);
|
||||
|
||||
void
|
||||
scrcpy_icon_destroy(SDL_Surface *icon);
|
||||
sc_icon_destroy(SDL_Surface *icon);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -892,7 +892,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
int32_t x = event->x;
|
||||
int32_t y = event->y;
|
||||
sc_screen_hidpi_scale_coords(im->screen, &x, &y);
|
||||
SDL_Rect *r = &im->screen->rect;
|
||||
SDL_FRect *r = &im->screen->rect;
|
||||
bool outside = x < r->x || x >= r->x + r->w
|
||||
|| y < r->y || y >= r->y + r->h;
|
||||
if (outside) {
|
||||
|
||||
@@ -949,7 +949,7 @@ aoa_complete:
|
||||
terminate_event_loop();
|
||||
LOGD("quit...");
|
||||
|
||||
if (options->video_playback) {
|
||||
if (options->window) {
|
||||
// Close the window immediately on closing, because screen_destroy()
|
||||
// may only be called once the video demuxer thread is joined (it may
|
||||
// take time)
|
||||
|
||||
194
app/src/screen.c
194
app/src/screen.c
@@ -144,64 +144,106 @@ sc_screen_is_relative_mode(struct sc_screen *screen) {
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_update_content_rect(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
struct sc_size content_size = screen->content_size;
|
||||
// The drawable size is the window size * the HiDPI scale
|
||||
struct sc_size drawable_size =
|
||||
sc_sdl_get_window_size_in_pixels(screen->window);
|
||||
|
||||
SDL_Rect *rect = &screen->rect;
|
||||
|
||||
if (is_optimal_size(drawable_size, content_size)) {
|
||||
compute_rect(struct sc_size render_size, struct sc_size content_size,
|
||||
bool can_upscale, SDL_FRect *rect) {
|
||||
if (is_optimal_size(render_size, content_size)) {
|
||||
rect->x = 0;
|
||||
rect->y = 0;
|
||||
rect->w = drawable_size.width;
|
||||
rect->h = drawable_size.height;
|
||||
rect->w = render_size.width;
|
||||
rect->h = render_size.height;
|
||||
return;
|
||||
}
|
||||
|
||||
bool keep_width = content_size.width * drawable_size.height
|
||||
> content_size.height * drawable_size.width;
|
||||
if (!can_upscale && content_size.width <= render_size.width
|
||||
&& content_size.height <= render_size.height) {
|
||||
// Center without upscaling
|
||||
rect->x = (render_size.width - content_size.width) / 2.f;
|
||||
rect->y = (render_size.height - content_size.height) / 2.f;
|
||||
rect->w = content_size.width;
|
||||
rect->h = content_size.height;
|
||||
return;
|
||||
}
|
||||
|
||||
bool keep_width = content_size.width * render_size.height
|
||||
> content_size.height * render_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;
|
||||
rect->w = render_size.width;
|
||||
rect->h = (float) render_size.width * content_size.height
|
||||
/ content_size.width;
|
||||
rect->y = (render_size.height - rect->h) / 2.f;
|
||||
} 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;
|
||||
rect->h = render_size.height;
|
||||
rect->w = (float) render_size.height * content_size.width
|
||||
/ content_size.height;
|
||||
rect->x = (render_size.width - rect->w) / 2.f;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_update_content_rect(struct sc_screen *screen) {
|
||||
// Only upscale video frames, not icon
|
||||
bool can_upscale = screen->video;
|
||||
|
||||
struct sc_size render_size =
|
||||
sc_sdl_get_render_output_size(screen->renderer);
|
||||
compute_rect(render_size, screen->content_size, can_upscale, &screen->rect);
|
||||
}
|
||||
|
||||
// render the texture to the renderer
|
||||
//
|
||||
// Set the update_content_rect flag if the window or content size may have
|
||||
// changed, so that the content rectangle is recomputed
|
||||
static void
|
||||
sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
|
||||
assert(screen->video);
|
||||
assert(screen->has_video_window);
|
||||
assert(!screen->video || screen->has_video_window);
|
||||
|
||||
if (update_content_rect) {
|
||||
sc_screen_update_content_rect(screen);
|
||||
}
|
||||
|
||||
enum sc_display_result res =
|
||||
sc_display_render(&screen->display, &screen->rect, screen->orientation);
|
||||
(void) res; // any error already logged
|
||||
}
|
||||
SDL_Renderer *renderer = screen->renderer;
|
||||
sc_sdl_render_clear(renderer);
|
||||
|
||||
static void
|
||||
sc_screen_render_novideo(struct sc_screen *screen) {
|
||||
enum sc_display_result res =
|
||||
sc_display_render(&screen->display, NULL, SC_ORIENTATION_0);
|
||||
(void) res; // any error already logged
|
||||
SDL_FRect *geometry = &screen->rect;
|
||||
enum sc_orientation orientation = screen->orientation;
|
||||
|
||||
SDL_Texture *texture = sc_texture_get_sdl_texture(&screen->tex);
|
||||
if (!texture) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok;
|
||||
if (orientation == SC_ORIENTATION_0) {
|
||||
ok = SDL_RenderTexture(renderer, texture, NULL, geometry);
|
||||
} else {
|
||||
unsigned cw_rotation = sc_orientation_get_rotation(orientation);
|
||||
double angle = 90 * cw_rotation;
|
||||
|
||||
const SDL_FRect *dstrect = NULL;
|
||||
SDL_FRect rect;
|
||||
if (sc_orientation_is_swap(orientation)) {
|
||||
rect.x = geometry->x + (geometry->w - geometry->h) / 2.f;
|
||||
rect.y = geometry->y + (geometry->h - geometry->w) / 2.f;
|
||||
rect.w = geometry->h;
|
||||
rect.h = geometry->w;
|
||||
dstrect = ▭
|
||||
} else {
|
||||
dstrect = geometry;
|
||||
}
|
||||
|
||||
SDL_FlipMode flip = sc_orientation_is_mirror(orientation)
|
||||
? SDL_FLIP_HORIZONTAL : 0;
|
||||
|
||||
ok = SDL_RenderTextureRotated(renderer, texture, NULL, dstrect, angle,
|
||||
NULL, flip);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
}
|
||||
sc_sdl_render_present(renderer);
|
||||
}
|
||||
|
||||
#if defined(__APPLE__) || defined(_WIN32)
|
||||
@@ -369,13 +411,25 @@ sc_screen_init(struct sc_screen *screen,
|
||||
goto error_destroy_fps_counter;
|
||||
}
|
||||
|
||||
ok = SDL_StartTextInput(screen->window);
|
||||
if (!ok) {
|
||||
LOGE("Could not enable text input: %s", SDL_GetError());
|
||||
screen->renderer = SDL_CreateRenderer(screen->window, NULL);
|
||||
if (!screen->renderer) {
|
||||
LOGE("Could not create renderer: %s", SDL_GetError());
|
||||
goto error_destroy_window;
|
||||
}
|
||||
|
||||
SDL_Surface *icon = scrcpy_icon_load();
|
||||
bool mipmaps = params->video;
|
||||
ok = sc_texture_init(&screen->tex, screen->renderer, mipmaps);
|
||||
if (!ok) {
|
||||
goto error_destroy_renderer;
|
||||
}
|
||||
|
||||
ok = SDL_StartTextInput(screen->window);
|
||||
if (!ok) {
|
||||
LOGE("Could not enable text input: %s", SDL_GetError());
|
||||
goto error_destroy_texture;
|
||||
}
|
||||
|
||||
SDL_Surface *icon = sc_icon_load_scrcpy();
|
||||
if (icon) {
|
||||
if (!SDL_SetWindowIcon(screen->window, icon)) {
|
||||
LOGW("Could not set window icon: %s", SDL_GetError());
|
||||
@@ -386,24 +440,28 @@ sc_screen_init(struct sc_screen *screen,
|
||||
} else {
|
||||
// without video, the icon is used as window content, it must be present
|
||||
LOGE("Could not load icon");
|
||||
goto error_destroy_window;
|
||||
goto error_destroy_texture;
|
||||
}
|
||||
|
||||
SDL_Surface *icon_novideo = params->video ? NULL : icon;
|
||||
bool mipmaps = params->video && params->mipmaps;
|
||||
ok = sc_display_init(&screen->display, screen->window, icon_novideo,
|
||||
mipmaps);
|
||||
if (icon) {
|
||||
scrcpy_icon_destroy(icon);
|
||||
if (!params->video) {
|
||||
assert(icon);
|
||||
screen->content_size.width = icon->w;
|
||||
screen->content_size.height = icon->h;
|
||||
ok = sc_texture_set_from_surface(&screen->tex, icon);
|
||||
if (!ok) {
|
||||
sc_icon_destroy(icon);
|
||||
goto error_destroy_texture;
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
goto error_destroy_window;
|
||||
|
||||
if (icon) {
|
||||
sc_icon_destroy(icon);
|
||||
}
|
||||
|
||||
screen->frame = av_frame_alloc();
|
||||
if (!screen->frame) {
|
||||
LOG_OOM();
|
||||
goto error_destroy_display;
|
||||
goto error_destroy_texture;
|
||||
}
|
||||
|
||||
struct sc_input_manager_params im_params = {
|
||||
@@ -459,8 +517,10 @@ sc_screen_init(struct sc_screen *screen,
|
||||
|
||||
return true;
|
||||
|
||||
error_destroy_display:
|
||||
sc_display_destroy(&screen->display);
|
||||
error_destroy_texture:
|
||||
sc_texture_destroy(&screen->tex);
|
||||
error_destroy_renderer:
|
||||
SDL_DestroyRenderer(screen->renderer);
|
||||
error_destroy_window:
|
||||
SDL_DestroyWindow(screen->window);
|
||||
error_destroy_fps_counter:
|
||||
@@ -522,8 +582,9 @@ sc_screen_destroy(struct sc_screen *screen) {
|
||||
#ifndef NDEBUG
|
||||
assert(!screen->open);
|
||||
#endif
|
||||
sc_display_destroy(&screen->display);
|
||||
sc_texture_destroy(&screen->tex);
|
||||
av_frame_free(&screen->frame);
|
||||
SDL_DestroyRenderer(screen->renderer);
|
||||
SDL_DestroyWindow(screen->window);
|
||||
sc_fps_counter_destroy(&screen->fps_counter);
|
||||
sc_frame_buffer_destroy(&screen->fb);
|
||||
@@ -606,6 +667,7 @@ sc_screen_apply_frame(struct sc_screen *screen) {
|
||||
if (!screen->has_frame
|
||||
|| screen->frame_size.width != new_frame_size.width
|
||||
|| screen->frame_size.height != new_frame_size.height) {
|
||||
|
||||
// frame dimension changed
|
||||
screen->frame_size = new_frame_size;
|
||||
|
||||
@@ -619,28 +681,12 @@ sc_screen_apply_frame(struct sc_screen *screen) {
|
||||
screen->has_frame = true;
|
||||
screen->content_size = new_content_size;
|
||||
}
|
||||
|
||||
enum sc_display_result res =
|
||||
sc_display_prepare_texture(&screen->display, screen->frame_size,
|
||||
frame->colorspace, frame->color_range);
|
||||
if (res == SC_DISPLAY_RESULT_ERROR) {
|
||||
return false;
|
||||
}
|
||||
if (res == SC_DISPLAY_RESULT_PENDING) {
|
||||
// Not an error, but do not continue
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
enum sc_display_result res =
|
||||
sc_display_update_texture(&screen->display, frame);
|
||||
if (res == SC_DISPLAY_RESULT_ERROR) {
|
||||
bool ok = sc_texture_set_from_frame(&screen->tex, frame);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
if (res == SC_DISPLAY_RESULT_PENDING) {
|
||||
// Not an error, but do not continue
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(screen->has_frame);
|
||||
if (!screen->has_video_window) {
|
||||
@@ -696,7 +742,10 @@ sc_screen_set_paused(struct sc_screen *screen, bool paused) {
|
||||
av_frame_free(&screen->frame);
|
||||
screen->frame = screen->resume_frame;
|
||||
screen->resume_frame = NULL;
|
||||
sc_screen_apply_frame(screen);
|
||||
bool ok = sc_screen_apply_frame(screen);
|
||||
if (!ok) {
|
||||
LOGE("Resume frame update failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (!paused) {
|
||||
@@ -778,14 +827,11 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
|
||||
bool ok = sc_screen_update_frame(screen);
|
||||
if (!ok) {
|
||||
LOGE("Frame update failed\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case SDL_EVENT_WINDOW_EXPOSED:
|
||||
if (!screen->video) {
|
||||
sc_screen_render_novideo(screen);
|
||||
} else if (screen->has_video_window) {
|
||||
if (!screen->video || screen->has_video_window) {
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
|
||||
#include "controller.h"
|
||||
#include "coords.h"
|
||||
#include "display.h"
|
||||
#include "fps_counter.h"
|
||||
#include "frame_buffer.h"
|
||||
#include "input_manager.h"
|
||||
#include "mouse_capture.h"
|
||||
#include "options.h"
|
||||
#include "texture.h"
|
||||
#include "trait/key_processor.h"
|
||||
#include "trait/frame_sink.h"
|
||||
#include "trait/mouse_processor.h"
|
||||
@@ -32,7 +32,7 @@ struct sc_screen {
|
||||
bool video;
|
||||
bool camera;
|
||||
|
||||
struct sc_display display;
|
||||
struct sc_texture tex;
|
||||
struct sc_input_manager im;
|
||||
struct sc_mouse_capture mc; // only used in mouse relative mode
|
||||
struct sc_frame_buffer fb;
|
||||
@@ -49,6 +49,7 @@ struct sc_screen {
|
||||
} req;
|
||||
|
||||
SDL_Window *window;
|
||||
SDL_Renderer *renderer;
|
||||
struct sc_size frame_size;
|
||||
struct sc_size content_size; // rotated frame_size
|
||||
|
||||
@@ -60,7 +61,7 @@ struct sc_screen {
|
||||
// client orientation
|
||||
enum sc_orientation orientation;
|
||||
// rectangle of the content (excluding black borders)
|
||||
struct SDL_Rect rect;
|
||||
struct SDL_FRect rect;
|
||||
bool has_frame;
|
||||
bool has_video_window;
|
||||
|
||||
|
||||
257
app/src/texture.c
Normal file
257
app/src/texture.c
Normal file
@@ -0,0 +1,257 @@
|
||||
#include "texture.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
sc_texture_init(struct sc_texture *tex, SDL_Renderer *renderer, bool mipmaps) {
|
||||
const char *renderer_name = SDL_GetRendererName(renderer);
|
||||
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
||||
|
||||
tex->mipmaps = false;
|
||||
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
display->gl_context = NULL;
|
||||
#endif
|
||||
|
||||
// starts with "opengl"
|
||||
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||
if (use_opengl) {
|
||||
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
// Persuade macOS to give us something better than OpenGL 2.1.
|
||||
// If we create a Core Profile context, we get the best OpenGL version.
|
||||
bool ok = SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
|
||||
SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
if (!ok) {
|
||||
LOGW("Could not set a GL Core Profile Context");
|
||||
}
|
||||
|
||||
LOGD("Creating OpenGL Core Profile context");
|
||||
display->gl_context = SDL_GL_CreateContext(window);
|
||||
if (!display->gl_context) {
|
||||
LOGE("Could not create OpenGL context: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct sc_opengl *gl = &tex->gl;
|
||||
sc_opengl_init(gl);
|
||||
|
||||
LOGI("OpenGL version: %s", gl->version);
|
||||
|
||||
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");
|
||||
tex->mipmaps = true;
|
||||
} else {
|
||||
LOGW("Trilinear filtering disabled "
|
||||
"(OpenGL 3.0+ or ES 2.0+ required)");
|
||||
}
|
||||
} else {
|
||||
LOGI("Trilinear filtering disabled");
|
||||
}
|
||||
} else if (mipmaps) {
|
||||
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
||||
}
|
||||
|
||||
tex->renderer = renderer;
|
||||
tex->texture = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_texture_destroy(struct sc_texture *tex) {
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GL_DestroyContext(tex->gl_context);
|
||||
#endif
|
||||
if (tex->texture) {
|
||||
SDL_DestroyTexture(tex->texture);
|
||||
}
|
||||
}
|
||||
|
||||
static enum SDL_Colorspace
|
||||
sc_texture_to_sdl_color_space(enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
bool full_range = color_range == AVCOL_RANGE_JPEG;
|
||||
|
||||
switch (color_space) {
|
||||
case AVCOL_SPC_BT709:
|
||||
case AVCOL_SPC_RGB:
|
||||
return full_range ? SDL_COLORSPACE_BT709_FULL
|
||||
: SDL_COLORSPACE_BT709_LIMITED;
|
||||
case AVCOL_SPC_BT470BG:
|
||||
case AVCOL_SPC_SMPTE170M:
|
||||
return full_range ? SDL_COLORSPACE_BT601_FULL
|
||||
: SDL_COLORSPACE_BT601_LIMITED;
|
||||
case AVCOL_SPC_BT2020_NCL:
|
||||
case AVCOL_SPC_BT2020_CL:
|
||||
return full_range ? SDL_COLORSPACE_BT2020_FULL
|
||||
: SDL_COLORSPACE_BT2020_LIMITED;
|
||||
default:
|
||||
return SDL_COLORSPACE_JPEG;
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_Texture *
|
||||
sc_texture_create_frame_texture(struct sc_texture *tex,
|
||||
struct sc_size size,
|
||||
enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
SDL_PropertiesID props = SDL_CreateProperties();
|
||||
if (!props) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
enum SDL_Colorspace sdl_color_space =
|
||||
sc_texture_to_sdl_color_space(color_space, color_range);
|
||||
|
||||
bool ok =
|
||||
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER,
|
||||
SDL_PIXELFORMAT_YV12);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER,
|
||||
SDL_TEXTUREACCESS_STREAMING);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER,
|
||||
size.width);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER,
|
||||
size.height);
|
||||
ok &= SDL_SetNumberProperty(props,
|
||||
SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER,
|
||||
sdl_color_space);
|
||||
|
||||
if (!ok) {
|
||||
LOGE("Could not set texture properties");
|
||||
SDL_DestroyProperties(props);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_Renderer *renderer = tex->renderer;
|
||||
SDL_Texture *texture = SDL_CreateTextureWithProperties(renderer, props);
|
||||
SDL_DestroyProperties(props);
|
||||
if (!texture) {
|
||||
LOGD("Could not create texture: %s", SDL_GetError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (tex->mipmaps) {
|
||||
struct sc_opengl *gl = &tex->gl;
|
||||
|
||||
SDL_PropertiesID props = SDL_GetTextureProperties(texture);
|
||||
if (!props) {
|
||||
LOGE("Could not get texture properties: %s", SDL_GetError());
|
||||
SDL_DestroyTexture(texture);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *renderer_name = SDL_GetRendererName(tex->renderer);
|
||||
const char *key = !renderer_name || !strcmp(renderer_name, "opengl")
|
||||
? SDL_PROP_TEXTURE_OPENGL_TEXTURE_NUMBER
|
||||
: SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_NUMBER;
|
||||
|
||||
int64_t texture_id = SDL_GetNumberProperty(props, key, 0);
|
||||
SDL_DestroyProperties(props);
|
||||
if (!texture_id) {
|
||||
LOGE("Could not get texture id: %s", SDL_GetError());
|
||||
SDL_DestroyTexture(texture);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(!(texture_id & ~0xFFFFFFFF)); // fits in uint32_t
|
||||
tex->texture_id = texture_id;
|
||||
gl->BindTexture(GL_TEXTURE_2D, tex->texture_id);
|
||||
|
||||
// Enable trilinear filtering for downscaling
|
||||
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
||||
GL_LINEAR_MIPMAP_LINEAR);
|
||||
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f);
|
||||
|
||||
gl->BindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_texture_set_from_frame(struct sc_texture *tex, const AVFrame *frame) {
|
||||
|
||||
struct sc_size size = {frame->width, frame->height};
|
||||
assert(size.width && size.height);
|
||||
|
||||
if (!tex->texture
|
||||
|| tex->texture_type != SC_TEXTURE_TYPE_FRAME
|
||||
|| tex->texture_size.width != size.width
|
||||
|| tex->texture_size.height != size.height) {
|
||||
// Incompatible texture, recreate it
|
||||
enum AVColorSpace color_space = frame->colorspace;
|
||||
enum AVColorRange color_range = frame->color_range;
|
||||
|
||||
if (tex->texture) {
|
||||
SDL_DestroyTexture(tex->texture);
|
||||
}
|
||||
|
||||
tex->texture = sc_texture_create_frame_texture(tex, size, color_space,
|
||||
color_range);
|
||||
if (!tex->texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tex->texture_size = size;
|
||||
tex->texture_type = SC_TEXTURE_TYPE_FRAME;
|
||||
|
||||
LOGI("Texture: %" PRIu16 "x%" PRIu16, size.width, size.height);
|
||||
}
|
||||
|
||||
assert(tex->texture);
|
||||
assert(tex->texture_type == SC_TEXTURE_TYPE_FRAME);
|
||||
|
||||
bool ok = SDL_UpdateYUVTexture(tex->texture, NULL,
|
||||
frame->data[0], frame->linesize[0],
|
||||
frame->data[1], frame->linesize[1],
|
||||
frame->data[2], frame->linesize[2]);
|
||||
if (!ok) {
|
||||
LOGD("Could not update texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tex->mipmaps) {
|
||||
assert(tex->texture_id);
|
||||
struct sc_opengl *gl = &tex->gl;
|
||||
|
||||
gl->BindTexture(GL_TEXTURE_2D, tex->texture_id);
|
||||
gl->GenerateMipmap(GL_TEXTURE_2D);
|
||||
gl->BindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_texture_set_from_surface(struct sc_texture *tex, SDL_Surface *surface) {
|
||||
if (tex->texture) {
|
||||
SDL_DestroyTexture(tex->texture);
|
||||
}
|
||||
|
||||
tex->texture = SDL_CreateTextureFromSurface(tex->renderer, surface);
|
||||
if (!tex->texture) {
|
||||
LOGE("Could not create texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
tex->texture_size.width = surface->w;
|
||||
tex->texture_size.height = surface->h;
|
||||
tex->texture_type = SC_TEXTURE_TYPE_ICON;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SDL_Texture *
|
||||
sc_texture_get_sdl_texture(struct sc_texture *tex) {
|
||||
return tex->texture;
|
||||
}
|
||||
54
app/src/texture.h
Normal file
54
app/src/texture.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#ifndef SC_DISPLAY_H
|
||||
#define SC_DISPLAY_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <libavutil/frame.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "opengl.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
#endif
|
||||
|
||||
enum sc_texture_type {
|
||||
SC_TEXTURE_TYPE_FRAME,
|
||||
SC_TEXTURE_TYPE_ICON,
|
||||
};
|
||||
|
||||
struct sc_texture {
|
||||
SDL_Renderer *renderer; // owned by the caller
|
||||
SDL_Texture *texture;
|
||||
// Only valid if texture != NULL
|
||||
struct sc_size texture_size;
|
||||
enum sc_texture_type texture_type;
|
||||
|
||||
struct sc_opengl gl;
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GLContext gl_context;
|
||||
#endif
|
||||
|
||||
bool mipmaps;
|
||||
uint32_t texture_id; // only set if mipmaps is enabled
|
||||
};
|
||||
|
||||
bool
|
||||
sc_texture_init(struct sc_texture *tex, SDL_Renderer *renderer, bool mipmaps);
|
||||
|
||||
void
|
||||
sc_texture_destroy(struct sc_texture *tex);
|
||||
|
||||
bool
|
||||
sc_texture_set_from_frame(struct sc_texture *tex, const AVFrame *frame);
|
||||
|
||||
bool
|
||||
sc_texture_set_from_surface(struct sc_texture *tex, SDL_Surface *surface);
|
||||
|
||||
SDL_Texture *
|
||||
sc_texture_get_sdl_texture(struct sc_texture *tex);
|
||||
|
||||
#endif
|
||||
@@ -60,8 +60,7 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
|
||||
goto error_destroy_window;
|
||||
}
|
||||
|
||||
SDL_Surface *icon = scrcpy_icon_load();
|
||||
|
||||
SDL_Surface *icon = sc_icon_load_scrcpy();
|
||||
if (icon) {
|
||||
bool ok = SDL_SetWindowIcon(screen->window, icon);
|
||||
if (!ok) {
|
||||
@@ -77,7 +76,7 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
|
||||
}
|
||||
|
||||
screen->texture = SDL_CreateTextureFromSurface(screen->renderer, icon);
|
||||
scrcpy_icon_destroy(icon);
|
||||
sc_icon_destroy(icon);
|
||||
if (!screen->texture) {
|
||||
goto error_destroy_renderer;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,25 @@
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
char *
|
||||
sc_file_build_path(const char *dir, const char *name) {
|
||||
size_t dir_len = strlen(dir);
|
||||
size_t name_len = strlen(name);
|
||||
|
||||
size_t len = dir_len + name_len + 2; // +2: '/' and '\0'
|
||||
char *path = malloc(len);
|
||||
if (!path) {
|
||||
LOG_OOM();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(path, dir, dir_len);
|
||||
path[dir_len] = SC_PATH_SEPARATOR;
|
||||
// namelen + 1 to copy the final '\0'
|
||||
memcpy(&path[dir_len + 1], name, name_len + 1);
|
||||
return path;
|
||||
}
|
||||
|
||||
char *
|
||||
sc_file_get_local_path(const char *name) {
|
||||
char *executable_path = sc_file_get_executable_path();
|
||||
@@ -25,24 +44,9 @@ sc_file_get_local_path(const char *name) {
|
||||
|
||||
*p = '\0'; // modify executable_path in place
|
||||
char *dir = executable_path;
|
||||
size_t dirlen = strlen(dir);
|
||||
size_t namelen = strlen(name);
|
||||
|
||||
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
|
||||
char *file_path = malloc(len);
|
||||
if (!file_path) {
|
||||
LOG_OOM();
|
||||
free(executable_path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(file_path, dir, dirlen);
|
||||
file_path[dirlen] = SC_PATH_SEPARATOR;
|
||||
// namelen + 1 to copy the final '\0'
|
||||
memcpy(&file_path[dirlen + 1], name, namelen + 1);
|
||||
char *file_path = sc_file_build_path(dir, name);
|
||||
|
||||
free(executable_path);
|
||||
|
||||
return file_path;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,15 @@ sc_file_get_executable_path(void);
|
||||
char *
|
||||
sc_file_get_local_path(const char *name);
|
||||
|
||||
/**
|
||||
* Return the concatenation of dir, the path separator and the filename.
|
||||
*
|
||||
* The result must be freed by the caller using free(). It may return NULL on
|
||||
* error.
|
||||
*/
|
||||
char *
|
||||
sc_file_build_path(const char *dir, const char *filename);
|
||||
|
||||
/**
|
||||
* Indicate if the file exists and is not a directory
|
||||
*/
|
||||
|
||||
@@ -130,6 +130,25 @@ sc_sdl_hide_window(SDL_Window *window) {
|
||||
}
|
||||
}
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_render_output_size(SDL_Renderer *renderer) {
|
||||
int width;
|
||||
int height;
|
||||
bool ok = SDL_GetRenderOutputSize(renderer, &width, &height);
|
||||
if (!ok) {
|
||||
LOGE("Could not get render output size: %s", SDL_GetError());
|
||||
LOGE("Please report the error");
|
||||
// fatal error
|
||||
abort();
|
||||
}
|
||||
|
||||
struct sc_size size = {
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
return size;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_sdl_render_clear(SDL_Renderer *renderer) {
|
||||
bool ok = SDL_RenderClear(renderer);
|
||||
|
||||
@@ -34,6 +34,9 @@ sc_sdl_show_window(SDL_Window *window);
|
||||
void
|
||||
sc_sdl_hide_window(SDL_Window *window);
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_render_output_size(SDL_Renderer *renderer);
|
||||
|
||||
bool
|
||||
sc_sdl_render_clear(SDL_Renderer *renderer);
|
||||
|
||||
|
||||
@@ -38,6 +38,6 @@ ninja -C "$LINUX_BUILD_DIR"
|
||||
# Group intermediate outputs into a 'dist' directory
|
||||
mkdir -p "$LINUX_BUILD_DIR/dist"
|
||||
cp "$LINUX_BUILD_DIR"/app/scrcpy "$LINUX_BUILD_DIR/dist/"
|
||||
cp app/data/icon.png "$LINUX_BUILD_DIR/dist/"
|
||||
cp app/data/scrcpy.png "$LINUX_BUILD_DIR/dist/"
|
||||
cp app/scrcpy.1 "$LINUX_BUILD_DIR/dist/"
|
||||
cp -r "$ADB_INSTALL_DIR"/. "$LINUX_BUILD_DIR/dist/"
|
||||
|
||||
@@ -38,6 +38,6 @@ ninja -C "$MACOS_BUILD_DIR"
|
||||
# Group intermediate outputs into a 'dist' directory
|
||||
mkdir -p "$MACOS_BUILD_DIR/dist"
|
||||
cp "$MACOS_BUILD_DIR"/app/scrcpy "$MACOS_BUILD_DIR/dist/"
|
||||
cp app/data/icon.png "$MACOS_BUILD_DIR/dist/"
|
||||
cp app/data/scrcpy.png "$MACOS_BUILD_DIR/dist/"
|
||||
cp app/scrcpy.1 "$MACOS_BUILD_DIR/dist/"
|
||||
cp -r "$ADB_INSTALL_DIR"/. "$MACOS_BUILD_DIR/dist/"
|
||||
|
||||
Reference in New Issue
Block a user