/*
* This file is part of mpv video player.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include "sub/osd.h"
#include "video/fmt-conversion.h"
#include "video/mp_image.h"
#include "video/sws_utils.h"
#include "vo.h"
#include "wayland_common.h"
struct buffer {
struct vo *vo;
size_t size;
struct wl_shm_pool *pool;
struct wl_buffer *buffer;
struct mp_image mpi;
struct buffer *next;
};
struct priv {
struct mp_sws_context *sws;
struct buffer *free_buffers;
struct mp_rect src;
struct mp_rect dst;
struct mp_osd_res osd;
};
static void buffer_handle_release(void *data, struct wl_buffer *wl_buffer)
{
struct buffer *buf = data;
struct vo *vo = buf->vo;
struct priv *p = vo->priv;
if (buf->mpi.w == vo->dwidth && buf->mpi.h == vo->dheight) {
buf->next = p->free_buffers;
p->free_buffers = buf;
} else {
talloc_free(buf);
}
}
static const struct wl_buffer_listener buffer_listener = {
buffer_handle_release,
};
static void buffer_destroy(void *p)
{
struct buffer *buf = p;
wl_buffer_destroy(buf->buffer);
wl_shm_pool_destroy(buf->pool);
munmap(buf->mpi.planes[0], buf->size);
}
static const struct wl_callback_listener frame_listener;
static void frame_callback(void *data, struct wl_callback *callback, uint32_t time)
{
struct vo_wayland_state *wl = data;
if (callback)
wl_callback_destroy(callback);
wl->frame_callback = wl_surface_frame(wl->surface);
wl_callback_add_listener(wl->frame_callback, &frame_listener, wl);
wl->frame_wait = false;
}
static const struct wl_callback_listener frame_listener = {
frame_callback,
};
static int allocate_memfd(size_t size)
{
int fd = memfd_create("mpv", MFD_CLOEXEC | MFD_ALLOW_SEALING);
if (fd < 0)
return -1;
fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
if (posix_fallocate(fd, 0, size) == 0)
return fd;
close(fd);
return -1;
}
static struct buffer *buffer_create(struct vo *vo, int width, int height)
{
struct priv *p = vo->priv;
struct vo_wayland_state *wl = vo->wl;
int fd;
int stride;
size_t size;
uint8_t *data;
struct buffer *buf;
stride = MP_ALIGN_UP(width * 4, 16);
size = height * stride;
fd = allocate_memfd(size);
if (fd < 0)
goto error0;
data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED)
goto error1;
buf = talloc_zero(NULL, struct buffer);
if (!buf)
goto error2;
buf->vo = vo;
buf->size = size;
mp_image_set_params(&buf->mpi, &p->sws->dst);
mp_image_set_size(&buf->mpi, width, height);
buf->mpi.planes[0] = data;
buf->mpi.stride[0] = stride;
buf->pool = wl_shm_create_pool(wl->shm, fd, size);
if (!buf->pool)
goto error3;
buf->buffer = wl_shm_pool_create_buffer(buf->pool, 0, width, height,
stride, WL_SHM_FORMAT_XRGB8888);
if (!buf->buffer)
goto error4;
wl_buffer_add_listener(buf->buffer, &buffer_listener, buf);
if (!wl->frame_callback) {
wl->frame_callback = wl_surface_frame(wl->surface);
wl_callback_add_listener(wl->frame_callback, &frame_listener, wl);
}
close(fd);
talloc_set_destructor(buf, buffer_destroy);
return buf;
error4:
wl_shm_pool_destroy(buf->pool);
error3:
talloc_free(buf);
error2:
munmap(data, size);
error1:
close(fd);
error0:
return NULL;
}
static int preinit(struct vo *vo)
{
struct priv *p = vo->priv;
if (!vo_wayland_init(vo))
return -1;
p->sws = mp_sws_alloc(vo);
p->sws->log = vo->log;
mp_sws_enable_cmdline_opts(p->sws, vo->global);
return 0;
}
static int query_format(struct vo *vo, int format)
{
return sws_isSupportedInput(imgfmt2pixfmt(format));
}
static int reconfig(struct vo *vo, struct mp_image_params *params)
{
struct priv *p = vo->priv;
if (!vo_wayland_reconfig(vo))
return -1;
p->sws->src = *params;
return 0;
}
static int resize(struct vo *vo)
{
struct priv *p = vo->priv;
struct vo_wayland_state *wl = vo->wl;
const int32_t width = wl->scaling * mp_rect_w(wl->geometry);
const int32_t height = wl->scaling * mp_rect_h(wl->geometry);
struct buffer *buf;
struct wl_region *region = wl_compositor_create_region(wl->compositor);
wl_region_add(region, 0, 0, width, height);
wl_surface_set_opaque_region(wl->surface, region);
wl_region_destroy(region);
vo->want_redraw = true;
vo->dwidth = width;
vo->dheight = height;
vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd);
p->sws->dst = (struct mp_image_params) {
.imgfmt = IMGFMT_BGR0,
.w = width,
.h = height,
.p_w = 1,
.p_h = 1,
};
mp_image_params_guess_csp(&p->sws->dst);
while (p->free_buffers) {
buf = p->free_buffers;
p->free_buffers = buf->next;
talloc_free(buf);
}
int ret = mp_sws_reinit(p->sws);
if (!wl->vo_opts->fullscreen && !wl->vo_opts->window_maximized)
wl_surface_commit(wl->surface);
return ret;
}
static int control(struct vo *vo, uint32_t request, void *data)
{
int events = 0;
int ret = vo_wayland_control(vo, &events, request, data);
if (events & VO_EVENT_RESIZE)
ret = resize(vo);
if (events & VO_EVENT_EXPOSE)
vo->want_redraw = true;
vo_event(vo, events);
return ret;
}
static void draw_image(struct vo *vo, struct mp_image *src)
{
struct priv *p = vo->priv;
struct vo_wayland_state *wl = vo->wl;
struct buffer *buf;
if (wl->hidden)
return;
wl->frame_wait = true;
buf = p->free_buffers;
if (buf) {
p->free_buffers = buf->next;
} else {
buf = buffer_create(vo, vo->dwidth, vo->dheight);
if (!buf) {
wl_surface_attach(wl->surface, NULL, 0, 0);
return;
}
}
if (src) {
struct mp_image dst = buf->mpi;
struct mp_rect src_rc;
struct mp_rect dst_rc;
src_rc.x0 = MP_ALIGN_DOWN(p->src.x0, MPMAX(src->fmt.align_x, 4));
src_rc.y0 = MP_ALIGN_DOWN(p->src.y0, MPMAX(src->fmt.align_y, 4));
src_rc.x1 = p->src.x1 - (p->src.x0 - src_rc.x0);
src_rc.y1 = p->src.y1 - (p->src.y0 - src_rc.y0);
dst_rc.x0 = MP_ALIGN_DOWN(p->dst.x0, MPMAX(dst.fmt.align_x, 4));
dst_rc.y0 = MP_ALIGN_DOWN(p->dst.y0, MPMAX(dst.fmt.align_y, 4));
dst_rc.x1 = p->dst.x1 - (p->dst.x0 - dst_rc.x0);
dst_rc.y1 = p->dst.y1 - (p->dst.y0 - dst_rc.y0);
mp_image_crop_rc(src, src_rc);
mp_image_crop_rc(&dst, dst_rc);
mp_sws_scale(p->sws, &dst, src);
if (dst_rc.y0 > 0)
mp_image_clear(&buf->mpi, 0, 0, buf->mpi.w, dst_rc.y0);
if (buf->mpi.h > dst_rc.y1)
mp_image_clear(&buf->mpi, 0, dst_rc.y1, buf->mpi.w, buf->mpi.h);
if (dst_rc.x0 > 0)
mp_image_clear(&buf->mpi, 0, dst_rc.y0, dst_rc.x0, dst_rc.y1);
if (buf->mpi.w > dst_rc.x1)
mp_image_clear(&buf->mpi, dst_rc.x1, dst_rc.y0, buf->mpi.w, dst_rc.y1);
osd_draw_on_image(vo->osd, p->osd, src->pts, 0, &buf->mpi);
} else {
mp_image_clear(&buf->mpi, 0, 0, buf->mpi.w, buf->mpi.h);
osd_draw_on_image(vo->osd, p->osd, 0, 0, &buf->mpi);
}
talloc_free(src);
wl_surface_attach(wl->surface, buf->buffer, 0, 0);
}
static void flip_page(struct vo *vo)
{
struct vo_wayland_state *wl = vo->wl;
wl_surface_damage(wl->surface, 0, 0, mp_rect_w(wl->geometry),
mp_rect_h(wl->geometry));
wl_surface_commit(wl->surface);
if (!wl->opts->disable_vsync)
vo_wayland_wait_frame(wl);
}
static void uninit(struct vo *vo)
{
struct priv *p = vo->priv;
struct buffer *buf;
while (p->free_buffers) {
buf = p->free_buffers;
p->free_buffers = buf->next;
talloc_free(buf);
}
vo_wayland_uninit(vo);
}
#define OPT_BASE_STRUCT struct priv
static const m_option_t options[] = {
{0}
};
const struct vo_driver video_out_wlshm = {
.description = "Wayland SHM video output (software scaling)",
.name = "wlshm",
.preinit = preinit,
.query_format = query_format,
.reconfig = reconfig,
.control = control,
.draw_image = draw_image,
.flip_page = flip_page,
.wakeup = vo_wayland_wakeup,
.wait_events = vo_wayland_wait_events,
.uninit = uninit,
.priv_size = sizeof(struct priv),
.options = options,
};