diff --git a/DOCS/man/en/vo.rst b/DOCS/man/en/vo.rst index 695ed12fe7..c78f6ad148 100644 --- a/DOCS/man/en/vo.rst +++ b/DOCS/man/en/vo.rst @@ -755,3 +755,13 @@ Available video output drivers are: JPEG DPI (default: 72) ``outdir=`` Specify the directory to save the image files to (default: ``./``). + +``wayland`` (Wayland only) + Wayland shared memory video output as fallback for ``opengl``. + + ``default-format`` + Use the default RGB32 format instead of an auto-detected one. + + ``alpha`` + Use a buffer format that supports videos and images with alpha + informations diff --git a/Makefile b/Makefile index 290700774d..e5a0c576b8 100644 --- a/Makefile +++ b/Makefile @@ -115,6 +115,7 @@ SOURCES-$(VAAPI) += video/out/vo_vaapi.c \ SOURCES-$(X11) += video/out/vo_x11.c video/out/x11_common.c SOURCES-$(XV) += video/out/vo_xv.c +SOURCES-$(WAYLAND) += video/out/vo_wayland.c video/out/wayland_common.c SOURCES-$(VF_LAVFI) += video/filter/vf_lavfi.c SOURCES-$(AF_LAVFI) += audio/filter/af_lavfi.c diff --git a/configure b/configure index d0d6e3235c..576ef5bbdf 100755 --- a/configure +++ b/configure @@ -1707,10 +1707,15 @@ if test "$_wayland" != no; then _wayland="no" pkg_config_add "wayland-client >= 1.0.0 wayland-egl >= 9.0.0 wayland-cursor >= 1.0.0 xkbcommon >= 0.3.0" \ && _wayland="yes" +fi +if test "$_wayland" = yes; then res_comment="" + def_wayland='#define CONFIG_WAYLAND' + vomodules="wayland $vomodules" else - _wayland="no" + def_wayland='#undef CONFIG_WAYLAND' res_comment="" + novomodules="wayland $novomodules" fi echores "$_wayland" diff --git a/video/out/vo.c b/video/out/vo.c index 014fcf03ad..d134163141 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -62,6 +62,7 @@ extern struct vo_driver video_out_direct3d_shaders; extern struct vo_driver video_out_sdl; extern struct vo_driver video_out_corevideo; extern struct vo_driver video_out_vaapi; +extern struct vo_driver video_out_wayland; const struct vo_driver *video_out_drivers[] = { @@ -104,6 +105,9 @@ const struct vo_driver *video_out_drivers[] = #endif #ifdef CONFIG_GL &video_out_opengl_hq, +#endif +#ifdef CONFIG_WAYLAND + &video_out_wayland, #endif NULL }; diff --git a/video/out/vo_wayland.c b/video/out/vo_wayland.c new file mode 100644 index 0000000000..f6c68a8e73 --- /dev/null +++ b/video/out/vo_wayland.c @@ -0,0 +1,645 @@ +/* + * This file is part of mpv video player. + * Copyright © 2013 Alexander Preisinger + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mpv. If not, see . + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "config.h" +#include "vo.h" +#include "sub/sub.h" +#include "video/vfcap.h" +#include "video/mp_image.h" +#include "mpvcore/mp_msg.h" +#include "video/sws_utils.h" + +#include "wayland_common.h" + +#define MAX_BUFFERS 2 + +static void draw_image(struct vo *vo, mp_image_t *mpi); + +static const struct wl_callback_listener frame_listener; +static const struct wl_buffer_listener buffer_listener; +static const struct wl_shm_listener shm_listener; + +struct fmtentry { + enum wl_shm_format wl_fmt; + enum mp_imgfmt mp_fmt; + bool hw_support; +}; + +// the first 2 Formats should be available on most platforms +// all other formats are optional +// the waylad byte order is reversed +static struct fmtentry fmttable[] = { + {WL_SHM_FORMAT_ARGB8888, IMGFMT_BGRA, false}, // 8b 8g 8r 8a + {WL_SHM_FORMAT_XRGB8888, IMGFMT_BGR0, false}, + {WL_SHM_FORMAT_RGB332, IMGFMT_BGR8, false}, // 3b 3g 2r + {WL_SHM_FORMAT_BGR233, IMGFMT_RGB8, false}, // 3r 3g 3b, + {WL_SHM_FORMAT_XRGB4444, IMGFMT_BGR12_LE, false}, // 4b 4g 4r 4a + {WL_SHM_FORMAT_XBGR4444, IMGFMT_RGB12_LE, false}, // 4r 4g 4b 4a + {WL_SHM_FORMAT_RGBX4444, IMGFMT_RGB12_BE, false}, // 4a 4b 4g 4r + {WL_SHM_FORMAT_BGRX4444, IMGFMT_BGR12_BE, false}, // 4a 4r 4g 4b + {WL_SHM_FORMAT_ARGB4444, IMGFMT_BGR12_LE, false}, + {WL_SHM_FORMAT_ABGR4444, IMGFMT_RGB12_LE, false}, + {WL_SHM_FORMAT_RGBA4444, IMGFMT_RGB12_BE, false}, + {WL_SHM_FORMAT_BGRA4444, IMGFMT_BGR12_BE, false}, + {WL_SHM_FORMAT_XRGB1555, IMGFMT_BGR15_LE, false}, // 5b 5g 5r 1a + {WL_SHM_FORMAT_XBGR1555, IMGFMT_RGB15_LE, false}, // 5r 5g 5b 1a + {WL_SHM_FORMAT_RGBX5551, IMGFMT_RGB15_BE, false}, // 1a 5g 5b 5r + {WL_SHM_FORMAT_BGRX5551, IMGFMT_BGR15_BE, false}, // 1a 5r 5g 5b + {WL_SHM_FORMAT_ARGB1555, IMGFMT_BGR15_LE, false}, + {WL_SHM_FORMAT_ABGR1555, IMGFMT_RGB15_LE, false}, + {WL_SHM_FORMAT_RGBA5551, IMGFMT_RGB15_BE, false}, + {WL_SHM_FORMAT_BGRA5551, IMGFMT_BGR15_BE, false}, + {WL_SHM_FORMAT_RGB565, IMGFMT_BGR16_LE, false}, // 5b 6g 5r + {WL_SHM_FORMAT_BGR565, IMGFMT_RGB16_LE, false}, // 5r 6g 5b + {WL_SHM_FORMAT_RGB888, IMGFMT_BGR24, false}, // 8b 8g 8r + {WL_SHM_FORMAT_BGR888, IMGFMT_RGB24, false}, // 8r 8g 8b + {WL_SHM_FORMAT_XBGR8888, IMGFMT_RGB0, false}, + {WL_SHM_FORMAT_RGBX8888, IMGFMT_0BGR, false}, + {WL_SHM_FORMAT_BGRX8888, IMGFMT_0RGB, false}, + {WL_SHM_FORMAT_ABGR8888, IMGFMT_RGBA, false}, + {WL_SHM_FORMAT_RGBA8888, IMGFMT_ABGR, false}, + {WL_SHM_FORMAT_BGRA8888, IMGFMT_ARGB, false}, +}; + +#define MAX_FORMAT_ENTRIES (sizeof(fmttable) / sizeof(fmttable[0])) +#define DEFAULT_FORMAT_ENTRY 1 +#define DEFAULT_ALPHA_FORMAT_ENTRY 0 + +struct buffer { + struct wl_buffer *wlbuf; + bool is_busy; + bool is_new; + void *shm_data; + size_t shm_size; +}; + +struct priv { + struct vo *vo; + struct vo_wayland_state *wl; + + struct fmtentry *pref_format; + int bytes_per_pixel; + int stride; + + struct mp_rect src; + struct mp_rect dst; + int src_w, src_h; + int dst_w, dst_h; + struct mp_osd_res osd; + + struct mp_sws_context *sws; + struct mp_image_params in_format; + + struct wl_callback *redraw_callback; + + struct buffer buffers[MAX_BUFFERS]; + struct buffer *front_buffer; + struct buffer *back_buffer; + + struct mp_image *original_image; + int width; // width of the original image + int height; + + // options + int enable_alpha; + int use_default; +}; + +/* copied from weston clients */ +static int set_cloexec_or_close(int fd) +{ + long flags; + + if (fd == -1) + return -1; + + if ((flags = fcntl(fd, F_GETFD)) == -1) + goto err; + + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + goto err; + + return fd; + +err: + close(fd); + return -1; +} + +static int create_tmpfile_cloexec(char *tmpname) +{ + int fd; + +#ifdef HAVE_MKOSTEMP + fd = mkostemp(tmpname, O_CLOEXEC); + if (fd >= 0) + unlink(tmpname); +#else + fd = mkstemp(tmpname); + if (fd >= 0) { + fd = set_cloexec_or_close(fd); + unlink(tmpname); + } +#endif + + return fd; +} + +static int os_create_anonymous_file(off_t size) +{ + static const char template[] = "/mpv-temp-XXXXXX"; + const char *path; + char *name; + int fd; + + path = getenv("XDG_RUNTIME_DIR"); + if (!path) { + errno = ENOENT; + return -1; + } + + name = malloc(strlen(path) + sizeof(template)); + if (!name) + return -1; + + strcpy(name, path); + strcat(name, template); + + fd = create_tmpfile_cloexec(name); + + free(name); + + if (fd < 0) + return -1; + + if (ftruncate(fd, size) < 0) { + close(fd); + return -1; + } + + return fd; +} + +static void buffer_swap(struct priv *p) +{ + if (!p->back_buffer->is_new) + return; + + struct buffer *tmp = p->back_buffer; + p->back_buffer = p->front_buffer; + p->front_buffer = tmp; + + // after swap set is_new to false, but keep busy + p->back_buffer->is_new = false; +} + + +// returns NULL if the back_buffer contains a new image or if the back buffer +// is busy (unlikely) +static struct buffer * buffer_get_back(struct priv *p) +{ + if (p->back_buffer->is_new || p->back_buffer->is_busy) + return NULL; + + p->back_buffer->is_busy = true; + + return p->back_buffer; +} + +static bool buffer_finalise_back(struct priv *p) +{ + p->back_buffer->is_new = true; + p->back_buffer->is_busy = false; + + return true; +} + +static struct buffer * buffer_get_front(struct priv *p) +{ + p->front_buffer->is_busy = true; + return p->front_buffer; +} + +static bool buffer_finalise_front(struct priv *p) +{ + p->front_buffer->is_new = false; // is_busy is reset on handle_release + return true; +} + +static struct mp_image buffer_get_mp_image(struct priv *p, struct buffer *buf) +{ + struct mp_image img = {0}; + mp_image_set_params(&img, &p->sws->dst); + + img.planes[0] = buf->shm_data; + img.stride[0] = p->stride; + + return img; +} + +static bool create_shm_buffer(struct priv *p, + struct buffer *buffer, + int width, + int height, + uint32_t format) +{ + struct wl_shm_pool *pool; + int fd, size; + void *data; + + p->stride = FFALIGN(width * p->bytes_per_pixel, SWS_MIN_BYTE_ALIGN); + size = p->stride * height; + + fd = os_create_anonymous_file(size); + if (fd < 0) { + MP_ERR(p->vo, "creating a buffer file for %d B failed: %m", size); + return false; + } + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + MP_ERR(p->vo, "mmap failed: %m\n"); + close(fd); + return false; + } + + pool = wl_shm_create_pool(p->wl->display.shm, fd, size); + buffer->wlbuf = wl_shm_pool_create_buffer(pool, 0, width, height, + p->stride, format); + wl_buffer_add_listener(buffer->wlbuf, &buffer_listener, buffer); + wl_shm_pool_destroy(pool); + close(fd); + + buffer->shm_size = size; + buffer->shm_data = data; + buffer->is_new = false; + buffer->is_busy = false; + return true; +} + +static void destroy_shm_buffer(struct buffer *buffer) +{ + if (buffer->wlbuf) { + wl_buffer_destroy(buffer->wlbuf); + buffer->wlbuf = NULL; + } + if (buffer->shm_data) { + munmap(buffer->shm_data, buffer->shm_size); + buffer->shm_data = NULL; + buffer->shm_size = 0; + } +} + +static bool reinit_shm_buffers(struct priv *p, int width, int height) +{ + bool ret = true; + enum wl_shm_format fmt = p->pref_format->wl_fmt; + for (int i = 0; ret && i < MAX_BUFFERS; ++i) { + destroy_shm_buffer(&p->buffers[i]); + ret = create_shm_buffer(p, &p->buffers[i], width, height, fmt); + } + return ret; +} + +static bool redraw_frame(struct priv *p) +{ + if (!p->original_image) + return false; + + draw_image(p->vo, p->original_image); + return true; +} + +static mp_image_t *get_screenshot(struct priv *p) +{ + if (!p->original_image) + return NULL; + + return mp_image_new_ref(p->original_image); +} + +static bool resize(struct priv *p) +{ + struct vo_wayland_state *wl = p->wl; + int32_t x = wl->window.sh_x; + int32_t y = wl->window.sh_y; + wl->vo->dwidth = wl->window.sh_width; + wl->vo->dheight = wl->window.sh_height; + + vo_get_src_dst_rects(p->vo, &p->src, &p->dst, &p->osd); + p->src_w = p->src.x1 - p->src.x0; + p->src_h = p->src.y1 - p->src.y0; + p->dst_w = p->dst.x1 - p->dst.x0; + p->dst_h = p->dst.y1 - p->dst.y0; + + MP_DBG(p->vo, "resizing %dx%d -> %dx%d\n", wl->window.width, + wl->window.height, + p->dst_w, + p->dst_h); + + if (x != 0) + x = wl->window.width - p->dst_w; + + if (y != 0) + y = wl->window.height - p->dst_h; + + mp_sws_set_from_cmdline(p->sws); + p->sws->src = p->in_format; + p->sws->dst = (struct mp_image_params) { + .imgfmt = p->pref_format->mp_fmt, + .w = p->dst_w, + .h = p->dst_h, + .d_w = p->dst_w, + .d_h = p->dst_h, + }; + + mp_image_params_guess_csp(&p->sws->dst); + + if (mp_sws_reinit(p->sws) < 0) + return false; + + if (!reinit_shm_buffers(p, p->dst_w, p->dst_h)) { + MP_ERR(p->vo, "failed to resize buffers\n"); + return false; + } + + wl->window.width = p->dst_w; + wl->window.height = p->dst_h; + + // a redraw should happen at this point + wl_surface_attach(wl->window.surface, p->front_buffer->wlbuf, x, y); + wl_surface_damage(wl->window.surface, 0, 0, p->dst_w, p->dst_h); + wl_surface_commit(wl->window.surface); + + p->wl->window.events = 0; + p->vo->want_redraw = true; + return true; +} + + +/* wayland listeners */ + +static void buffer_handle_release(void *data, struct wl_buffer *buffer) +{ + struct buffer *buf = data; + buf->is_busy = false; +} + +static const struct wl_buffer_listener buffer_listener = { + buffer_handle_release +}; + +static void frame_handle_redraw(void *data, + struct wl_callback *callback, + uint32_t time) +{ + struct priv *p = data; + struct vo_wayland_state *wl = p->wl; + struct buffer *buf = buffer_get_front(p); + + wl_surface_attach(wl->window.surface, buf->wlbuf, 0, 0); + wl_surface_damage(wl->window.surface, 0, 0, p->dst_w, p->dst_h); + + if (callback) + wl_callback_destroy(callback); + + p->redraw_callback = wl_surface_frame(wl->window.surface); + wl_callback_add_listener(p->redraw_callback, &frame_listener, p); + wl_surface_commit(wl->window.surface); + buffer_finalise_front(p); +} + +static const struct wl_callback_listener frame_listener = { + frame_handle_redraw +}; + +static void shm_handle_format(void *data, + struct wl_shm *wl_shm, + uint32_t format) +{ + struct priv *p = data; + for (int i = 0; i < MAX_FORMAT_ENTRIES; ++i) { + if (fmttable[i].wl_fmt == format) { + MP_INFO(p->vo, "format %s supported by hw\n", + mp_imgfmt_to_name(fmttable[i].mp_fmt)); + fmttable[i].hw_support = true; + } + } +} + +static const struct wl_shm_listener shm_listener = { + shm_handle_format +}; + + +/* mpv interface */ + +static void draw_image(struct vo *vo, mp_image_t *mpi) +{ + struct priv *p = vo->priv; + struct buffer *buf = buffer_get_back(p); + + if (!buf) + return; + + struct mp_image src = *mpi; + struct mp_rect src_rc = p->src; + src_rc.x0 = MP_ALIGN_DOWN(src_rc.x0, src.fmt.align_x); + src_rc.y0 = MP_ALIGN_DOWN(src_rc.y0, src.fmt.align_y); + mp_image_crop_rc(&src, src_rc); + + struct mp_image img = buffer_get_mp_image(p, buf); + mp_sws_scale(p->sws, &img, &src); + + mp_image_setrefp(&p->original_image, mpi); + buffer_finalise_back(p); +} + +static void draw_osd(struct vo *vo, struct osd_state *osd) +{ + struct priv *p = vo->priv; + struct mp_image img = buffer_get_mp_image(p, p->back_buffer); + osd_draw_on_image(osd, p->osd, osd->vo_pts, 0, &img); +} + +static void flip_page(struct vo *vo) +{ + struct priv *p = vo->priv; + + buffer_swap(p); + + if (!p->redraw_callback) + frame_handle_redraw(p, NULL, 0); +} + +static int query_format(struct vo *vo, uint32_t format) +{ + for (int i = 0; i < MAX_FORMAT_ENTRIES; ++i) { + if (fmttable[i].mp_fmt == format && fmttable[i].hw_support) + return VFCAP_CSP_SUPPORTED_BY_HW | VFCAP_CSP_SUPPORTED; + } + + if (mp_sws_supported_format(format)) + return VFCAP_CSP_SUPPORTED; + + return 0; +} + +static int reconfig(struct vo *vo, struct mp_image_params *fmt, int flags) +{ + struct priv *p = vo->priv; + + p->width = vo->dwidth; + p->height = vo->dheight; + p->in_format = *fmt; + + // find the same format first + for (int i = 0; !p->pref_format && i < MAX_FORMAT_ENTRIES; ++i) { + if (fmttable[i].mp_fmt == fmt->imgfmt && fmttable[i].hw_support) + p->pref_format = &fmttable[i]; + } + + // if the format is not supported choose one of the fancy formats next + // the default formats are always first so begin with the last + for (int i = MAX_FORMAT_ENTRIES-1; !p->pref_format && i >= 0; --i) { + if (fmttable[i].hw_support) + p->pref_format = &fmttable[i]; + } + + if (p->use_default) + p->pref_format = &fmttable[DEFAULT_FORMAT_ENTRY]; + + if (p->enable_alpha) + p->pref_format = &fmttable[DEFAULT_ALPHA_FORMAT_ENTRY]; + + p->bytes_per_pixel = mp_imgfmt_get_desc(p->pref_format->mp_fmt).bytes[0]; + MP_VERBOSE(vo, "bytes per pixel: %d\n", p->bytes_per_pixel); + + if (!reinit_shm_buffers(p, p->width, p->height)) { + MP_ERR(vo, "failed to initialise buffers\n"); + return -1; + } + + vo_wayland_config(vo, vo->dwidth, vo->dheight, flags); + + return 0; +} + +static void uninit(struct vo *vo) +{ + struct priv *p = vo->priv; + for (int i = 0; i < MAX_BUFFERS; ++i) + destroy_shm_buffer(&p->buffers[i]); + + vo_wayland_uninit(vo); +} + +static int preinit(struct vo *vo) +{ + struct priv *p = vo->priv; + + if (!vo_wayland_init(vo)) { + MP_ERR(vo, "could not initalise backend\n"); + return -1; + } + + p->vo = vo; + p->wl = vo->wayland; + p->sws = mp_sws_alloc(vo); + + p->front_buffer = &p->buffers[1]; + p->back_buffer = &p->buffers[0]; + + wl_shm_add_listener(p->wl->display.shm, &shm_listener, p); + wl_display_dispatch(p->wl->display.display); + return 0; +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + struct priv *p = vo->priv; + switch (request) { + case VOCTRL_GET_PANSCAN: + return VO_TRUE; + case VOCTRL_SET_PANSCAN: + resize(p); + return VO_TRUE; + case VOCTRL_REDRAW_FRAME: + return redraw_frame(p); + case VOCTRL_WINDOW_TO_OSD_COORDS: + { + // OSD is rendered into the scaled image + float *c = data; + struct mp_rect *dst = &p->dst; + c[0] = av_clipf(c[0], dst->x0, dst->x1) - dst->x0; + c[1] = av_clipf(c[1], dst->y0, dst->y1) - dst->y0; + return VO_TRUE; + } + case VOCTRL_SCREENSHOT: + { + struct voctrl_screenshot_args *args = data; + args->out_image = get_screenshot(p); + return true; + } + } + int events = 0; + int r = vo_wayland_control(vo, &events, request, data); + + // NOTE: VO_EVENT_EXPOSE is never returned by the wayland backend + if (events & VO_EVENT_RESIZE) + resize(p); + + return r; +} + +#define OPT_BASE_STRUCT struct priv +const struct vo_driver video_out_wayland = { + .info = &(const vo_info_t) { + "Wayland SHM video output", + "wayland", + "Alexander Preisinger ", + "" + }, + .priv_size = sizeof(struct priv), + .preinit = preinit, + .query_format = query_format, + .reconfig = reconfig, + .control = control, + .draw_image = draw_image, + .draw_osd = draw_osd, + .flip_page = flip_page, + .uninit = uninit, + .options = (const struct m_option[]) { + OPT_FLAG("alpha", enable_alpha, 0), + OPT_FLAG("default-format", use_default, 0), + {0} + }, +}; +