1
0
mirror of https://github.com/mpv-player/mpv synced 2024-12-16 20:05:07 +00:00
mpv/video/out/vo_wayland.c
Alexander Preisinger 0920dc1e26 wayland/shm: rewrite buffer handling
I was unhappy with the old way of handling buffers, especially resizing. But my
original plan to use wl_shm_pool_resize wasn't as good as I initially thought.
I might get back to it.

With the new buffer pools it now possible to select triple buffering. Also the
buffer pools are also needed for the upcoming subsurfaces for osd and subtitles.

I hope this change was worth it.
2014-02-11 18:44:53 +01:00

829 lines
22 KiB
C

/*
* This file is part of mpv video player.
* Copyright © 2013 Alexander Preisinger <alexander.preisinger@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <libavutil/common.h>
#include "config.h"
#include "vo.h"
#include "video/vfcap.h"
#include "video/mp_image.h"
#include "video/sws_utils.h"
#include "video/memcpy_pic.h"
#include "sub/osd.h"
#include "sub/img_convert.h"
#include "common/msg.h"
#include "wayland_common.h"
#include "wayland-version.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;
};
// the first 2 Formats should be available on most platforms
// all other formats are optional
// the waylad byte order is reversed
static const struct fmtentry fmttable[] = {
{WL_SHM_FORMAT_ARGB8888, IMGFMT_BGRA}, // 8b 8g 8r 8a
{WL_SHM_FORMAT_XRGB8888, IMGFMT_BGR0},
{WL_SHM_FORMAT_RGB332, IMGFMT_BGR8}, // 3b 3g 2r
{WL_SHM_FORMAT_BGR233, IMGFMT_RGB8}, // 3r 3g 3b,
{WL_SHM_FORMAT_XRGB4444, IMGFMT_BGR12_LE}, // 4b 4g 4r 4a
{WL_SHM_FORMAT_XBGR4444, IMGFMT_RGB12_LE}, // 4r 4g 4b 4a
{WL_SHM_FORMAT_RGBX4444, IMGFMT_RGB12_BE}, // 4a 4b 4g 4r
{WL_SHM_FORMAT_BGRX4444, IMGFMT_BGR12_BE}, // 4a 4r 4g 4b
{WL_SHM_FORMAT_ARGB4444, IMGFMT_BGR12_LE},
{WL_SHM_FORMAT_ABGR4444, IMGFMT_RGB12_LE},
{WL_SHM_FORMAT_RGBA4444, IMGFMT_RGB12_BE},
{WL_SHM_FORMAT_BGRA4444, IMGFMT_BGR12_BE},
{WL_SHM_FORMAT_XRGB1555, IMGFMT_BGR15_LE}, // 5b 5g 5r 1a
{WL_SHM_FORMAT_XBGR1555, IMGFMT_RGB15_LE}, // 5r 5g 5b 1a
{WL_SHM_FORMAT_RGBX5551, IMGFMT_RGB15_BE}, // 1a 5g 5b 5r
{WL_SHM_FORMAT_BGRX5551, IMGFMT_BGR15_BE}, // 1a 5r 5g 5b
{WL_SHM_FORMAT_ARGB1555, IMGFMT_BGR15_LE},
{WL_SHM_FORMAT_ABGR1555, IMGFMT_RGB15_LE},
{WL_SHM_FORMAT_RGBA5551, IMGFMT_RGB15_BE},
{WL_SHM_FORMAT_BGRA5551, IMGFMT_BGR15_BE},
{WL_SHM_FORMAT_RGB565, IMGFMT_BGR16_LE}, // 5b 6g 5r
{WL_SHM_FORMAT_BGR565, IMGFMT_RGB16_LE}, // 5r 6g 5b
{WL_SHM_FORMAT_RGB888, IMGFMT_BGR24}, // 8b 8g 8r
{WL_SHM_FORMAT_BGR888, IMGFMT_RGB24}, // 8r 8g 8b
{WL_SHM_FORMAT_XBGR8888, IMGFMT_RGB0},
{WL_SHM_FORMAT_RGBX8888, IMGFMT_0BGR},
{WL_SHM_FORMAT_BGRX8888, IMGFMT_0RGB},
{WL_SHM_FORMAT_ABGR8888, IMGFMT_RGBA},
{WL_SHM_FORMAT_RGBA8888, IMGFMT_ABGR},
{WL_SHM_FORMAT_BGRA8888, IMGFMT_ARGB},
};
#define MAX_FORMAT_ENTRIES (sizeof(fmttable) / sizeof(fmttable[0]))
#define DEFAULT_FORMAT_ENTRY 1
#define DEFAULT_ALPHA_FORMAT_ENTRY 0
struct priv;
struct buffer {
struct wl_buffer *wlbuf;
bool is_busy;
bool is_new;
bool is_attached;
bool to_resize;
void *shm_data;
size_t shm_size;
};
struct buffer_pool {
struct buffer *buffers;
struct buffer *front_buffer; // just pointers to any of the buffers
struct buffer *middle_buffer; // just pointers to any of the buffers
struct buffer *back_buffer;
uint32_t buffer_no;
uint32_t size;
uint32_t stride;
uint32_t bytes_per_pixel;
enum wl_shm_format format; // TODO use fmtentry here
struct wl_shm *shm;
};
struct supported_format {
const struct fmtentry *fmt;
bool is_alpha;
struct wl_list link;
};
struct priv {
struct vo *vo;
struct vo_wayland_state *wl;
struct wl_list format_list;
const struct fmtentry *video_format;
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_pool video_bufpool;
struct buffer *attached_buffer;
struct mp_image *original_image;
int width; // width of the original image
int height;
int x, y; // coords for resizing
// options
int enable_alpha;
int use_rgb565;
int use_triplebuffering;
};
/* 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 bool is_alpha_format(const struct fmtentry *fmt)
{
return !!(mp_imgfmt_get_desc(fmt->mp_fmt).flags & MP_IMGFLAG_ALPHA);
}
static const struct fmtentry * is_wayland_format_supported(struct priv *p,
enum wl_shm_format fmt)
{
struct supported_format *sf;
// find the matching format first
wl_list_for_each(sf, &p->format_list, link) {
if (sf->fmt->wl_fmt == fmt) {
return sf->fmt;
}
}
return NULL;
}
// buffer functions
static bool buffer_finalise_back(struct buffer *buf)
{
buf->is_new = true;
return true;
}
static bool buffer_finalise_front(struct buffer *buf)
{
buf->is_new = false; // is_busy is reset on handle_release
buf->is_busy = true;
buf->is_attached = true;
return true;
}
static void buffer_destroy_content(struct buffer *buf)
{
if (buf->wlbuf) {
wl_buffer_destroy(buf->wlbuf);
buf->wlbuf = NULL;
}
if (buf->shm_data) {
munmap(buf->shm_data, buf->shm_size);
buf->shm_data = NULL;
buf->shm_size = 0;
}
}
static bool buffer_create_content(struct buffer_pool *pool,
struct buffer *buf,
int width,
int height)
{
int fd;
void *data;
struct wl_shm_pool *shm_pool;
fd = os_create_anonymous_file(pool->size);
if (fd < 0) {
return false;
}
data = mmap(NULL, pool->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
close(fd);
return false;
}
// wl-buffers of the same shm_pool share it's content which might be useful
// if we resize the buffers (from the docs).
shm_pool = wl_shm_create_pool(pool->shm, fd, pool->size);
buf->wlbuf = wl_shm_pool_create_buffer(shm_pool, 0, width, height,
pool->stride, pool->format);
wl_buffer_add_listener(buf->wlbuf, &buffer_listener, buf);
wl_shm_pool_destroy(shm_pool);
close(fd);
buf->shm_size = pool->size;
buf->shm_data = data;
buf->is_new = false;
buf->is_busy = false;
return true;
}
static bool buffer_resize(struct buffer_pool *pool, struct buffer *buf,
uint32_t width, uint32_t height)
{
if (buf->is_attached) {
buf->to_resize = true;
return true;
}
if (buf->shm_size == pool->size)
return true;
buf->to_resize = false;
buffer_destroy_content(buf);
return buffer_create_content(pool, buf, width, height);
}
static struct mp_image buffer_get_mp_image(struct priv *p,
struct buffer_pool *pool,
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] = pool->stride;
return img;
}
// buffer pool functions
static void buffer_pool_init(struct priv *p,
struct buffer_pool *pool,
uint32_t buffer_no,
uint32_t width, uint32_t height,
const struct fmtentry *fmt,
struct wl_shm *shm)
{
pool->shm = shm;
pool->buffers = calloc(buffer_no, sizeof(struct buffer));
pool->buffer_no = buffer_no;
pool->format = fmt->wl_fmt;
pool->bytes_per_pixel = mp_imgfmt_get_desc(fmt->mp_fmt).bytes[0];
pool->stride = FFALIGN(width * pool->bytes_per_pixel, SWS_MIN_BYTE_ALIGN);
pool->size = pool->stride * height;
for (uint32_t i = 0; i < buffer_no; ++i)
buffer_create_content(pool, &pool->buffers[i], width, height);
if (buffer_no == 3) {
pool->back_buffer = &pool->buffers[0];
pool->middle_buffer = &pool->buffers[1];
pool->front_buffer = &pool->buffers[2];
}
else if (buffer_no == 2) {
pool->back_buffer = &pool->buffers[0];
pool->front_buffer = &pool->buffers[1];
pool->middle_buffer = NULL;
}
else {
pool->back_buffer = NULL;
pool->middle_buffer = NULL;
pool->front_buffer = NULL;
}
}
static bool buffer_pool_resize(struct buffer_pool *pool,
int width,
int height)
{
bool ret = true;
pool->stride = FFALIGN(width * pool->bytes_per_pixel, SWS_MIN_BYTE_ALIGN);
pool->size = pool->stride * height;
for (uint32_t i = 0; ret && i < pool->buffer_no; ++i)
ret = buffer_resize(pool, &pool->buffers[i], width, height);
return ret;
}
static void buffer_pool_destroy(struct buffer_pool *pool)
{
for (uint32_t i = 0; i < pool->buffer_no; ++i)
buffer_destroy_content(&pool->buffers[i]);
free(pool->buffers);
pool->front_buffer = NULL;
pool->back_buffer = NULL;
pool->buffers = NULL;
}
static void buffer_pool_swap(struct buffer_pool *pool)
{
if (pool->buffer_no == 3) {
if (pool->back_buffer->is_new) {
struct buffer *tmp = pool->back_buffer;
pool->back_buffer = pool->middle_buffer;
pool->middle_buffer = tmp;
}
if (!pool->front_buffer->is_busy && !pool->front_buffer->is_new) {
struct buffer *tmp = pool->front_buffer;
pool->front_buffer = pool->middle_buffer;
pool->middle_buffer = tmp;
}
}
else if (pool->buffer_no == 2) {
if (pool->back_buffer->is_new) {
struct buffer *tmp = pool->back_buffer;
pool->back_buffer = pool->front_buffer;
pool->front_buffer = tmp;
}
}
}
// returns NULL if the back buffer is busy
static struct buffer * buffer_pool_get_back(struct buffer_pool *pool)
{
if (!pool->back_buffer || pool->back_buffer->is_busy)
return NULL;
return pool->back_buffer;
}
// returns NULL if the front buffer is not new
static struct buffer * buffer_pool_get_front(struct buffer_pool *pool)
{
if (!pool->front_buffer || !pool->front_buffer->is_new)
return NULL;
pool->front_buffer->is_busy = true;
return pool->front_buffer;
}
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(wl, "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->video_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 (!buffer_pool_resize(&p->video_bufpool, p->dst_w, p->dst_h)) {
MP_ERR(wl, "failed to resize buffers\n");
return false;
}
wl->window.width = p->dst_w;
wl->window.height = p->dst_h;
// if no alpha enabled format is used then create an opaque region to allow
// the compositor to optimize the drawing of the window
if (!p->enable_alpha) {
struct wl_region *opaque =
wl_compositor_create_region(wl->display.compositor);
wl_region_add(opaque, 0, 0, p->dst_w, p->dst_h);
wl_surface_set_opaque_region(wl->window.surface, opaque);
wl_region_destroy(opaque);
}
p->x = x;
p->y = y;
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;
buffer_pool_swap(&p->video_bufpool);
struct buffer *buf = buffer_pool_get_front(&p->video_bufpool);
if (buf) {
wl_surface_attach(wl->window.surface, buf->wlbuf, p->x, p->y);
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);
// resize attached buffer
if (p->attached_buffer) {
p->attached_buffer->is_attached = false;
buffer_resize(&p->video_bufpool, p->attached_buffer, p->dst_w, p->dst_h);
}
p->attached_buffer = buf;
buffer_finalise_front(buf);
p->x = 0;
p->y = 0;
}
else {
if (callback)
wl_callback_destroy(callback);
p->redraw_callback = NULL;
}
}
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 (uint32_t i = 0; i < MAX_FORMAT_ENTRIES; ++i) {
if (fmttable[i].wl_fmt == format) {
MP_INFO(p->wl, "format %s supported by hw\n",
mp_imgfmt_to_name(fmttable[i].mp_fmt));
struct supported_format *sf = talloc(p, struct supported_format);
sf->fmt = &fmttable[i];
sf->is_alpha = is_alpha_format(sf->fmt);
wl_list_insert(&p->format_list, &sf->link);
}
}
}
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_pool_get_back(&p->video_bufpool);
if (!buf) {
MP_VERBOSE(p->wl, "can't draw, back buffer is busy\n");
return;
}
if (buf->to_resize) {
if (buf->is_attached) {
MP_WARN(p->wl, "resizing attached buffer, use triple-buffering\n");
buf->is_attached = false;
}
buffer_resize(&p->video_bufpool, buf, p->dst_w, p->dst_h);
}
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, &p->video_bufpool, buf);
mp_sws_scale(p->sws, &img, &src);
mp_image_setrefp(&p->original_image, mpi);
buffer_finalise_back(buf);
}
static void draw_osd(struct vo *vo, struct osd_state *osd)
{
struct priv *p = vo->priv;
struct buffer *buf = buffer_pool_get_back(&p->video_bufpool);
if (buf) {
struct mp_image img = buffer_get_mp_image(p, &p->video_bufpool, buf);
osd_draw_on_image(osd, p->osd, osd_get_vo_pts(osd), 0, &img);
}
}
static void flip_page(struct vo *vo)
{
struct priv *p = vo->priv;
buffer_pool_swap(&p->video_bufpool);
if (!p->redraw_callback) {
MP_DBG(p->wl, "restart frame callback\n");
frame_handle_redraw(p, NULL, 0);
}
}
static int query_format(struct vo *vo, uint32_t format)
{
struct priv *p = vo->priv;
struct supported_format *sf;
wl_list_for_each_reverse(sf, &p->format_list, link) {
if (sf->fmt->mp_fmt == format)
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;
mp_image_unrefp(&p->original_image);
p->width = fmt->w;
p->height = fmt->h;
p->in_format = *fmt;
struct supported_format *sf;
// find the matching format first
wl_list_for_each(sf, &p->format_list, link) {
if (sf->fmt->mp_fmt == fmt->imgfmt && (p->enable_alpha == sf->is_alpha)) {
p->video_format = sf->fmt;
break;
}
}
if (!p->video_format) {
// if use default is enable overwrite the auto selected one
if (p->enable_alpha)
p->video_format = &fmttable[DEFAULT_ALPHA_FORMAT_ENTRY];
else
p->video_format = &fmttable[DEFAULT_FORMAT_ENTRY];
}
// overides alpha
// use rgb565 if performance is your main concern
if (p->use_rgb565) {
const struct fmtentry *fmt =
is_wayland_format_supported(p, WL_SHM_FORMAT_RGB565);
if (fmt)
p->video_format = fmt;
}
buffer_pool_init(p, &p->video_bufpool, (p->use_triplebuffering ? 3 : 2),
p->width, p->height, p->video_format, p->wl->display.shm);
vo_wayland_config(vo, vo->dwidth, vo->dheight, flags);
if (p->wl->window.events & VO_EVENT_RESIZE)
resize(p);
return 0;
}
static void uninit(struct vo *vo)
{
struct priv *p = vo->priv;
buffer_pool_destroy(&p->video_bufpool);
if (p->redraw_callback)
wl_callback_destroy(p->redraw_callback);
talloc_free(p->original_image);
vo_wayland_uninit(vo);
}
static int preinit(struct vo *vo)
{
struct priv *p = vo->priv;
if (!vo_wayland_init(vo))
return -1;
p->vo = vo;
p->wl = vo->wayland;
p->sws = mp_sws_alloc(vo);
wl_list_init(&p->format_list);
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 = {
.description = "Wayland SHM video output",
.name = "wayland",
.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("rgb565", use_rgb565, 0),
OPT_FLAG("triple-buffering", use_triplebuffering, 0),
{0}
},
};