/*
* This file is part of mpv.
*
* 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 "config.h"
#if HAVE_VAAPI
#include
#endif
#if HAVE_DRM
#include
#endif
#include "mpv_talloc.h"
#include "common/global.h"
#include "vo.h"
#include "video/mp_image.h"
#include "gpu/hwdec.h"
#include "gpu/video.h"
#if HAVE_VAAPI
#include "video/vaapi.h"
#endif
#include "present_sync.h"
#include "wayland_common.h"
#include "wlbuf_pool.h"
// Generated from wayland-protocols
#include "generated/wayland/linux-dmabuf-unstable-v1.h"
#include "generated/wayland/viewporter.h"
#if HAVE_WAYLAND_PROTOCOLS_1_27
#include "generated/wayland/single-pixel-buffer-v1.h"
#endif
struct priv {
struct mp_log *log;
struct ra_ctx *ctx;
struct mpv_global *global;
struct ra_hwdec_ctx hwdec_ctx;
int events;
struct wl_shm_pool *solid_buffer_pool;
struct wl_buffer *solid_buffer;
struct wlbuf_pool *wlbuf_pool;
bool want_reset;
uint64_t reset_count;
struct mp_rect src;
bool resized;
#if HAVE_VAAPI
VADisplay display;
#endif
};
#if HAVE_VAAPI
static uintptr_t vaapi_key_provider(struct mp_image *src)
{
return va_surface_id(src);
}
static void close_file_descriptors(VADRMPRIMESurfaceDescriptor desc)
{
for (int i = 0; i < desc.num_objects; i++)
close(desc.objects[i].fd);
}
/* va-api dmabuf importer */
static bool vaapi_dmabuf_importer(struct mp_image *src, struct wlbuf_pool_entry* entry,
struct zwp_linux_buffer_params_v1 *params)
{
struct priv *p = entry->vo->priv;
VADRMPRIMESurfaceDescriptor desc = { 0 };
/* composed has single layer */
int layer_no = 0;
VAStatus status = vaExportSurfaceHandle(p->display, entry->key, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
VA_EXPORT_SURFACE_COMPOSED_LAYERS | VA_EXPORT_SURFACE_READ_ONLY, &desc);
if (!CHECK_VA_STATUS(entry->vo, "vaExportSurfaceHandle()")) {
/* invalid surface warning => composed layers not supported */
if (status == VA_STATUS_ERROR_INVALID_SURFACE)
MP_VERBOSE(entry->vo, "vaExportSurfaceHandle: composed layers not supported.\n");
close_file_descriptors(desc);
return false;
}
bool success = false;
uint32_t drm_format = desc.layers[layer_no].drm_format;
if (!vo_wayland_supported_format(entry->vo, drm_format, desc.objects[0].drm_format_modifier)) {
MP_VERBOSE(entry->vo, "%s(%016lx) is not supported.\n",
mp_tag_str(drm_format), desc.objects[0].drm_format_modifier);
goto done;
}
entry->drm_format = drm_format;
for (int plane_no = 0; plane_no < desc.layers[layer_no].num_planes; ++plane_no) {
int object = desc.layers[layer_no].object_index[plane_no];
uint64_t modifier = desc.objects[object].drm_format_modifier;
zwp_linux_buffer_params_v1_add(params, desc.objects[object].fd, plane_no, desc.layers[layer_no].offset[plane_no],
desc.layers[layer_no].pitch[plane_no], modifier >> 32, modifier & 0xffffffff);
}
success = true;
done:
close_file_descriptors(desc);
return success;
}
#endif
#if HAVE_DRM
static uintptr_t drmprime_key_provider(struct mp_image *src)
{
struct AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)src->planes[0];
AVDRMObjectDescriptor object = desc->objects[0];
return (uintptr_t)object.fd;
}
static bool drmprime_dmabuf_importer(struct mp_image *src, struct wlbuf_pool_entry *entry,
struct zwp_linux_buffer_params_v1 *params)
{
int layer_no, plane_no;
const AVDRMFrameDescriptor *avdesc = (AVDRMFrameDescriptor *)src->planes[0];
for (layer_no = 0; layer_no < avdesc->nb_layers; layer_no++) {
AVDRMLayerDescriptor layer = avdesc->layers[layer_no];
entry->drm_format = layer.format;
for (plane_no = 0; plane_no < layer.nb_planes; ++plane_no) {
AVDRMPlaneDescriptor plane = layer.planes[plane_no];
int object_index = plane.object_index;
AVDRMObjectDescriptor object = avdesc->objects[object_index];
uint64_t modifier = object.format_modifier;
zwp_linux_buffer_params_v1_add(params, object.fd, plane_no, plane.offset,
plane.pitch, modifier >> 32, modifier & 0xffffffff);
}
}
return true;
}
#endif
static void set_viewport_source(struct vo *vo, struct mp_rect src) {
struct priv *p = vo->priv;
struct vo_wayland_state *wl = vo->wl;
if (wl->video_viewport && !mp_rect_equals(&p->src, &src)) {
// 1. update viewport source
wp_viewport_set_source(wl->video_viewport, src.x0 << 8,
src.y0 << 8, mp_rect_w(src) << 8,
mp_rect_h(src) << 8);
// 2. reset buffer pool
p->want_reset = true;
p->reset_count = 0;
// 3. update to new src dimensions
p->src = src;
}
}
static void resize(struct vo *vo)
{
struct priv *p = vo->priv;
struct vo_wayland_state *wl = vo->wl;
struct mp_rect src;
struct mp_rect dst;
struct mp_osd_res osd;
struct mp_vo_opts *vo_opts = wl->vo_opts;
const int width = mp_rect_w(wl->geometry);
const int height = mp_rect_h(wl->geometry);
vo_wayland_set_opaque_region(wl, 0);
vo->dwidth = width;
vo->dheight = height;
// top level viewport is calculated with pan set to zero
vo->opts->pan_x = 0;
vo->opts->pan_y = 0;
vo_get_src_dst_rects(vo, &src, &dst, &osd);
if (wl->viewport)
wp_viewport_set_destination(wl->viewport, 2 * dst.x0 + mp_rect_w(dst), 2 * dst.y0 + mp_rect_h(dst));
//now we restore pan for video viewport caculation
vo->opts->pan_x = vo_opts->pan_x;
vo->opts->pan_y = vo_opts->pan_y;
vo_get_src_dst_rects(vo, &src, &dst, &osd);
if (wl->video_viewport)
wp_viewport_set_destination(wl->video_viewport, mp_rect_w(dst), mp_rect_h(dst));
wl_subsurface_set_position(wl->video_subsurface, dst.x0, dst.y0);
set_viewport_source(vo, src);
vo->want_redraw = true;
p->resized = true;
}
static void draw_frame(struct vo *vo, struct vo_frame *frame)
{
struct priv *p = vo->priv;
struct vo_wayland_state *wl = vo->wl;
struct wlbuf_pool_entry *entry;
if (!vo_wayland_check_visible(vo))
return;
// ensure the pool is reset after hwdec seek,
// to avoid stutter artifact
p->reset_count++;
if (p->want_reset && p->reset_count <= 2){
wlbuf_pool_clean(p->wlbuf_pool);
if (p->reset_count == 2)
p->want_reset = false;
}
/* lazy initialization of buffer pool */
if (!p->wlbuf_pool) {
#if HAVE_VAAPI
p->display = (VADisplay)ra_get_native_resource(p->ctx->ra, "VADisplay");
if (p->display)
p->wlbuf_pool = wlbuf_pool_alloc(vo, wl, vaapi_key_provider, vaapi_dmabuf_importer);
#endif
#if HAVE_DRM
if (!p->wlbuf_pool)
p->wlbuf_pool = wlbuf_pool_alloc(vo, wl, drmprime_key_provider, drmprime_dmabuf_importer);
#endif
}
entry = wlbuf_pool_get_entry(p->wlbuf_pool, frame->current);
if (!entry)
return;
MP_VERBOSE(entry->vo, "Schedule buffer pool entry : %lu\n",entry->key );
wl_surface_attach(wl->video_surface, entry->buffer, 0, 0);
wl_surface_damage_buffer(wl->video_surface, 0, 0, INT32_MAX, INT32_MAX);
}
static void flip_page(struct vo *vo)
{
struct vo_wayland_state *wl = vo->wl;
wl_surface_commit(wl->video_surface);
wl_surface_commit(wl->surface);
if (!wl->opts->disable_vsync)
vo_wayland_wait_frame(wl);
if (wl->use_present)
present_sync_swap(wl->present);
}
static void get_vsync(struct vo *vo, struct vo_vsync_info *info)
{
struct vo_wayland_state *wl = vo->wl;
if (wl->use_present)
present_sync_get_info(wl->present, info);
}
static bool is_supported_fmt(int fmt)
{
return (fmt == IMGFMT_DRMPRIME || fmt == IMGFMT_VAAPI);
}
static int query_format(struct vo *vo, int format)
{
return is_supported_fmt(format);
}
static int reconfig(struct vo *vo, struct mp_image_params *params)
{
if (!vo_wayland_reconfig(vo))
return VO_ERROR;
return 0;
}
static void call_request_hwdec_api(void *ctx, struct hwdec_imgfmt_request *params)
{
// Roundabout way to run hwdec loading on the VO thread.
// Redirects to request_hwdec_api().
vo_control(ctx, VOCTRL_LOAD_HWDEC_API, params);
}
static int control(struct vo *vo, uint32_t request, void *data)
{
struct priv *p = vo->priv;
int events = 0;
int ret;
switch (request) {
case VOCTRL_SET_PANSCAN:
if (p->resized)
resize(vo);
return VO_TRUE;
case VOCTRL_LOAD_HWDEC_API:
assert(p->hwdec_ctx.ra);
struct hwdec_imgfmt_request* req = (struct hwdec_imgfmt_request*)data;
if (!is_supported_fmt(req->imgfmt))
return 0;
ra_hwdec_ctx_load_fmt(&p->hwdec_ctx, vo->hwdec_devs, req);
return (p->hwdec_ctx.num_hwdecs > 0);
break;
case VOCTRL_RESET:
p->want_reset = true;
p->reset_count = 0;
return VO_TRUE;
break;
}
ret = vo_wayland_control(vo, &events, request, data);
if (events & VO_EVENT_RESIZE)
resize(vo);
if (events & VO_EVENT_EXPOSE)
vo->want_redraw = true;
vo_event(vo, events);
return ret;
}
static void uninit(struct vo *vo)
{
struct priv *p = vo->priv;
if (p->solid_buffer_pool)
wl_shm_pool_destroy(p->solid_buffer_pool);
if (p->solid_buffer)
wl_buffer_destroy(p->solid_buffer);
ra_hwdec_ctx_uninit(&p->hwdec_ctx);
if (vo->hwdec_devs) {
hwdec_devices_set_loader(vo->hwdec_devs, NULL, NULL);
hwdec_devices_destroy(vo->hwdec_devs);
}
wlbuf_pool_free(p->wlbuf_pool);
vo_wayland_uninit(vo);
ra_ctx_destroy(&p->ctx);
}
static int preinit(struct vo *vo)
{
struct priv *p = vo->priv;
p->log = vo->log;
p->global = vo->global;
p->ctx = ra_ctx_create_by_name(vo, "wldmabuf");
if (!p->ctx)
goto err;
assert(p->ctx->ra);
if (!vo->wl->dmabuf || !vo->wl->dmabuf_feedback) {
MP_FATAL(vo->wl, "Compositor doesn't support the %s (ver. 4) protocol!\n",
zwp_linux_dmabuf_v1_interface.name);
goto err;
}
if (!vo->wl->shm) {
MP_FATAL(vo->wl, "Compositor doesn't support the %s protocol!\n",
wl_shm_interface.name);
goto err;
}
if (!vo->wl->video_subsurface) {
MP_FATAL(vo->wl, "Compositor doesn't support the %s protocol!\n",
wl_subcompositor_interface.name);
goto err;
}
if (!vo->wl->viewport) {
MP_FATAL(vo->wl, "Compositor doesn't support the %s protocol!\n",
wp_viewporter_interface.name);
goto err;
}
if (vo->wl->single_pixel_manager) {
#if HAVE_WAYLAND_PROTOCOLS_1_27
p->solid_buffer = wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer(
vo->wl->single_pixel_manager, 0, 0, 0, UINT32_MAX); /* R, G, B, A */
#endif
} else {
int width = 1;
int height = 1;
int stride = MP_ALIGN_UP(width * 4, 16);
int fd = vo_wayland_allocate_memfd(vo, stride);
if (fd < 0)
goto err;
p->solid_buffer_pool = wl_shm_create_pool(vo->wl->shm, fd, height * stride);
close(fd);
if (!p->solid_buffer_pool)
goto err;
p->solid_buffer = wl_shm_pool_create_buffer(
p->solid_buffer_pool, 0, width, height, stride, WL_SHM_FORMAT_XRGB8888);
}
if (!p->solid_buffer)
goto err;
wl_surface_attach(vo->wl->surface, p->solid_buffer, 0, 0);
vo->hwdec_devs = hwdec_devices_create();
hwdec_devices_set_loader(vo->hwdec_devs, call_request_hwdec_api, vo);
assert(!p->hwdec_ctx.ra);
p->hwdec_ctx = (struct ra_hwdec_ctx) {
.log = p->log,
.global = p->global,
.ra = p->ctx->ra,
};
ra_hwdec_ctx_init(&p->hwdec_ctx, vo->hwdec_devs, NULL, true);
p->src = (struct mp_rect){0, 0, 0, 0};
return 0;
err:
uninit(vo);
return -1;
}
const struct vo_driver video_out_dmabuf_wayland = {
.description = "Wayland dmabuf video output",
.name = "dmabuf-wayland",
.preinit = preinit,
.query_format = query_format,
.reconfig = reconfig,
.control = control,
.draw_frame = draw_frame,
.flip_page = flip_page,
.get_vsync = get_vsync,
.wakeup = vo_wayland_wakeup,
.wait_events = vo_wayland_wait_events,
.uninit = uninit,
.priv_size = sizeof(struct priv),
};