/*
 * 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 <http://www.gnu.org/licenses/>.
 */

#include <sys/mman.h>
#include <unistd.h>
#include "config.h"

#if HAVE_DRM
#include <libavutil/hwcontext_drm.h>
#endif

#if HAVE_VAAPI
#include <va/va_drmcommon.h>
#endif

#include "gpu/hwdec.h"
#include "gpu/video.h"
#include "mpv_talloc.h"
#include "present_sync.h"
#include "sub/draw_bmp.h"
#include "video/fmt-conversion.h"
#include "video/mp_image.h"
#include "vo.h"
#include "wayland_common.h"
#include "wldmabuf/ra_wldmabuf.h"

#if HAVE_VAAPI
#include "video/vaapi.h"
#endif

// Generated from wayland-protocols
#include "linux-dmabuf-v1.h"
#include "viewporter.h"
#include "single-pixel-buffer-v1.h"

// We need at least enough buffers to avoid a
// flickering artifact in certain formats.
#define WL_BUFFERS_WANTED 15

enum hwdec_type {
    HWDEC_NONE,
    HWDEC_VAAPI,
    HWDEC_DRMPRIME,
};

struct buffer {
    struct vo *vo;
    struct wl_buffer *buffer;
    struct wl_list link;
    struct vo_frame *frame;

    uint32_t drm_format;
    uintptr_t id;
};

struct osd_buffer {
    struct vo *vo;
    struct wl_buffer *buffer;
    struct wl_list link;
    struct mp_image image;
    size_t size;
    bool attached;
};

struct priv {
    struct mp_log *log;
    struct mp_rect src;
    struct mpv_global *global;

    struct ra_ctx *ctx;
    struct ra_hwdec_ctx hwdec_ctx;

    struct wl_shm_pool *solid_buffer_pool;
    struct wl_buffer *solid_buffer;
    struct wl_list buffer_list;
    struct wl_list osd_buffer_list;

    struct wl_shm_pool *osd_shm_pool;
    uint8_t *osd_shm_data;
    int osd_shm_width;
    int osd_shm_stride;
    int osd_shm_height;
    bool osd_surface_is_mapped;
    bool osd_surface_has_contents;

    struct osd_buffer *osd_buffer;
    struct mp_draw_sub_cache *osd_cache;
    struct mp_osd_res screen_osd_res;

    bool destroy_buffers;
    bool force_window;
    enum hwdec_type hwdec_type;

    struct mp_image_params target_params;
    uint32_t drm_format;
    uint64_t drm_modifier;
};

static void buffer_handle_release(void *data, struct wl_buffer *wl_buffer)
{
    struct buffer *buf = data;
    if (buf->frame) {
        talloc_free(buf->frame);
        buf->frame = NULL;
    }
}

static const struct wl_buffer_listener buffer_listener = {
    buffer_handle_release,
};

static void osd_buffer_handle_release(void *data, struct wl_buffer *wl_buffer)
{
    struct osd_buffer *osd_buf = data;
    osd_buf->attached = false;
}

static const struct wl_buffer_listener osd_buffer_listener = {
    osd_buffer_handle_release,
};

#if HAVE_VAAPI
static void close_file_descriptors(const VADRMPRIMESurfaceDescriptor *desc)
{
    for (int i = 0; i < desc->num_objects; i++)
        close(desc->objects[i].fd);
}
#endif

static uintptr_t vaapi_surface_id(struct mp_image *src)
{
    uintptr_t id = 0;
#if HAVE_VAAPI
    id = (uintptr_t)va_surface_id(src);
#endif
    return id;
}

static bool vaapi_drm_format(struct vo *vo, struct mp_image *src)
{
    bool format = false;
#if HAVE_VAAPI
    struct priv *p = vo->priv;
    VADRMPRIMESurfaceDescriptor desc = {0};

    uintptr_t id = vaapi_surface_id(src);
    VADisplay display = ra_get_native_resource(p->ctx->ra, "VADisplay");
    VAStatus status = vaExportSurfaceHandle(display, id, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
                                            VA_EXPORT_SURFACE_COMPOSED_LAYERS | VA_EXPORT_SURFACE_READ_ONLY, &desc);

    if (!CHECK_VA_STATUS(vo, "vaExportSurfaceHandle()")) {
        /* invalid surface warning => composed layers not supported */
        if (status == VA_STATUS_ERROR_INVALID_SURFACE)
            MP_VERBOSE(vo, "vaExportSurfaceHandle: composed layers not supported.\n");
        goto done;
    }
    p->drm_format = desc.layers[0].drm_format;
    p->drm_modifier = desc.objects[0].drm_format_modifier;
    format = true;
done:
    close_file_descriptors(&desc);
#endif
    return format;
}

static void vaapi_dmabuf_importer(struct buffer *buf, struct mp_image *src,
                                  struct zwp_linux_buffer_params_v1 *params)
{
#if HAVE_VAAPI
    struct vo *vo = buf->vo;
    struct priv *p = vo->priv;
    VADRMPRIMESurfaceDescriptor desc = {0};
    VADisplay display = ra_get_native_resource(p->ctx->ra, "VADisplay");

    /* composed has single layer */
    int layer_no = 0;
    buf->id = vaapi_surface_id(src);
    VAStatus status = vaExportSurfaceHandle(display, buf->id, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
                                            VA_EXPORT_SURFACE_COMPOSED_LAYERS | VA_EXPORT_SURFACE_READ_ONLY, &desc);

    if (!CHECK_VA_STATUS(vo, "vaExportSurfaceHandle()")) {
        /* invalid surface warning => composed layers not supported */
        if (status == VA_STATUS_ERROR_INVALID_SURFACE)
            MP_VERBOSE(vo, "vaExportSurfaceHandle: composed layers not supported.\n");
        goto done;
    }
    buf->drm_format = desc.layers[layer_no].drm_format;
    if (!ra_compatible_format(p->ctx->ra, buf->drm_format, desc.objects[0].drm_format_modifier)) {
        MP_VERBOSE(vo, "%s(%016" PRIx64 ") is not supported.\n",
                   mp_tag_str(buf->drm_format), desc.objects[0].drm_format_modifier);
        buf->drm_format = 0;
        goto done;
    }
    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);
    }

done:
    close_file_descriptors(&desc);
#endif
}

static uintptr_t drmprime_surface_id(struct mp_image *src)
{
    uintptr_t id = 0;
#if HAVE_DRM
    struct AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)src->planes[0];

    AVDRMObjectDescriptor object = desc->objects[0];
    id = (uintptr_t)object.fd;
#endif
    return id;
}

static bool drmprime_drm_format(struct vo *vo, struct mp_image *src)
{
    bool format = false;
#if HAVE_DRM
    struct priv *p = vo->priv;
    struct AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)src->planes[0];
    if (!desc)
        return format;

    // Just check the very first layer/plane.
    p->drm_format = desc->layers[0].format;
    int object_index = desc->layers[0].planes[0].object_index;
    p->drm_modifier = desc->objects[object_index].format_modifier;
    format = true;
#endif
    return format;
}

static void drmprime_dmabuf_importer(struct buffer *buf, struct mp_image *src,
                                     struct zwp_linux_buffer_params_v1 *params)
{
#if HAVE_DRM
    int layer_no, plane_no;
    int max_planes = 0;
    const AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)src->planes[0];
    if (!desc)
        return;

    buf->id = drmprime_surface_id(src);
    for (layer_no = 0; layer_no < desc->nb_layers; layer_no++) {
        AVDRMLayerDescriptor layer = desc->layers[layer_no];

        buf->drm_format = layer.format;
        max_planes = MPMAX(max_planes, layer.nb_planes);
        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 = desc->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);
        }
    }
#endif
}

static intptr_t surface_id(struct vo *vo, struct mp_image *src)
{
    struct priv *p = vo->priv;
    switch(p->hwdec_type) {
    case HWDEC_VAAPI:
        return vaapi_surface_id(src);
    case HWDEC_DRMPRIME:
        return drmprime_surface_id(src);
    default:
        return 0;
    }
}

static bool drm_format_check(struct vo *vo, struct mp_image *src)
{
    struct priv *p = vo->priv;
    switch(p->hwdec_type) {
    case HWDEC_VAAPI:
        return vaapi_drm_format(vo, src);
    case HWDEC_DRMPRIME:
        return drmprime_drm_format(vo, src);
    }
    return false;
}

static struct buffer *buffer_check(struct vo *vo, struct vo_frame *frame)
{
    struct priv *p = vo->priv;

    /* Make more buffers if we're not at the desired amount yet. */
    if (wl_list_length(&p->buffer_list) < WL_BUFFERS_WANTED)
        goto done;

    uintptr_t id = surface_id(vo, frame->current);
    struct buffer *buf;
    wl_list_for_each(buf, &p->buffer_list, link) {
        if (buf->id == id) {
            if (buf->frame)
                talloc_free(buf->frame);
            buf->frame = frame;
            return buf;
        }
    }

done:
    return NULL;
}

static struct buffer *buffer_create(struct vo *vo, struct vo_frame *frame)
{
    struct vo_wayland_state *wl = vo->wl;
    struct priv *p = vo->priv;

    struct buffer *buf = talloc_zero(vo, struct buffer);
    buf->vo = vo;
    buf->frame = frame;

    struct mp_image *image = buf->frame->current;
    struct zwp_linux_buffer_params_v1 *params = zwp_linux_dmabuf_v1_create_params(wl->dmabuf);

    switch(p->hwdec_type) {
    case HWDEC_VAAPI:
        vaapi_dmabuf_importer(buf, image, params);
        break;
    case HWDEC_DRMPRIME:
        drmprime_dmabuf_importer(buf, image, params);
        break;
    }

    if (!buf->drm_format) {
        talloc_free(buf->frame);
        talloc_free(buf);
        zwp_linux_buffer_params_v1_destroy(params);
        return NULL;
    }

    buf->buffer = zwp_linux_buffer_params_v1_create_immed(params, image->params.w, image->params.h,
                                                          buf->drm_format, 0);
    zwp_linux_buffer_params_v1_destroy(params);
    wl_buffer_add_listener(buf->buffer, &buffer_listener, buf);
    wl_list_insert(&p->buffer_list, &buf->link);
    return buf;
}

static struct buffer *buffer_get(struct vo *vo, struct vo_frame *frame)
{
    /* Reuse existing buffer if possible. */
    struct buffer *buf = buffer_check(vo, frame);
    if (buf) {
        return buf;
    } else {
        return buffer_create(vo, frame);
    }
}

static void destroy_buffers(struct vo *vo)
{
    struct priv *p = vo->priv;
    struct buffer *buf, *tmp;
    p->destroy_buffers = false;
    wl_list_for_each_safe(buf, tmp, &p->buffer_list, link) {
        wl_list_remove(&buf->link);
        if (buf->frame) {
            talloc_free(buf->frame);
            buf->frame = NULL;
        }
        if (buf->buffer) {
            wl_buffer_destroy(buf->buffer);
            buf->buffer = NULL;
        }
        talloc_free(buf);
    }
}

static void destroy_osd_buffers(struct vo *vo)
{
    if (!vo->wl)
        return;

    struct priv *p = vo->priv;
    struct osd_buffer *osd_buf, *tmp;
    wl_list_for_each_safe(osd_buf, tmp, &p->osd_buffer_list, link) {
        wl_list_remove(&osd_buf->link);
        munmap(osd_buf->image.planes[0], osd_buf->size);
        if (osd_buf->buffer) {
            wl_buffer_destroy(osd_buf->buffer);
            osd_buf->buffer = NULL;
        }
    }
}

static struct osd_buffer *osd_buffer_check(struct vo *vo)
{
    struct priv *p = vo->priv;
    struct osd_buffer *osd_buf;
    wl_list_for_each(osd_buf, &p->osd_buffer_list, link) {
        if (!osd_buf->attached)
            return osd_buf;
    }
    return NULL;
}

static struct osd_buffer *osd_buffer_create(struct vo *vo)
{
    struct priv *p = vo->priv;
    struct osd_buffer *osd_buf = talloc_zero(vo, struct osd_buffer);

    osd_buf->vo = vo;
    osd_buf->size = p->osd_shm_height * p->osd_shm_stride;
    mp_image_set_size(&osd_buf->image, p->osd_shm_width, p->osd_shm_height);
    osd_buf->image.planes[0] = p->osd_shm_data;
    osd_buf->image.stride[0] = p->osd_shm_stride;
    osd_buf->buffer = wl_shm_pool_create_buffer(p->osd_shm_pool, 0,
                                                p->osd_shm_width, p->osd_shm_height,
                                                p->osd_shm_stride, WL_SHM_FORMAT_ARGB8888);

    if (!osd_buf->buffer) {
        talloc_free(osd_buf);
        return NULL;
    }

    wl_list_insert(&p->osd_buffer_list, &osd_buf->link);
    wl_buffer_add_listener(osd_buf->buffer, &osd_buffer_listener, osd_buf);
    return osd_buf;
}

static struct osd_buffer *osd_buffer_get(struct vo *vo)
{
    struct osd_buffer *osd_buf = osd_buffer_check(vo);
    if (osd_buf) {
        return osd_buf;
    } else {
        return osd_buffer_create(vo);
    }
}

static void create_shm_pool(struct vo *vo)
{
    struct vo_wayland_state *wl = vo->wl;
    struct priv *p = vo->priv;

    int stride = MP_ALIGN_UP(vo->dwidth * 4, MP_IMAGE_BYTE_ALIGN);
    size_t size = vo->dheight * stride;
    int fd = vo_wayland_allocate_memfd(vo, size);
    if (fd < 0)
        return;
    uint8_t *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (data == MAP_FAILED)
        goto error1;
    struct wl_shm_pool *pool = wl_shm_create_pool(wl->shm, fd, size);
    if (!pool)
        goto error2;
    close(fd);

    destroy_osd_buffers(vo);

    if (p->osd_shm_pool)
        wl_shm_pool_destroy(p->osd_shm_pool);
    p->osd_shm_pool = pool;
    p->osd_shm_width = vo->dwidth;
    p->osd_shm_height = vo->dheight;
    p->osd_shm_stride = stride;
    p->osd_shm_data = data;
    return;

error2:
    munmap(data, size);
error1:
    close(fd);
}

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 (p->force_window)
        return;

    if (!mp_rect_equals(&p->src, &src)) {
        wp_viewport_set_source(wl->video_viewport, wl_fixed_from_int(src.x0),
                               wl_fixed_from_int(src.y0), wl_fixed_from_int(mp_rect_w(src)),
                               wl_fixed_from_int(mp_rect_h(src)));
        p->src = src;
    }
}

static void resize(struct vo *vo)
{
    struct vo_wayland_state *wl = vo->wl;
    struct priv *p = vo->priv;

    struct mp_rect src;
    struct mp_rect dst;
    struct mp_vo_opts *opts = wl->opts;

    const int width = mp_rect_w(wl->geometry);
    const int height = mp_rect_h(wl->geometry);
    vo_get_src_dst_rects(vo, &src, &dst, &p->screen_osd_res);

    if (width == 0 || height == 0 || mp_rect_w(dst) == 0 || mp_rect_h(dst) == 0)
        return;

    vo_wayland_set_opaque_region(wl, false);
    vo->dwidth = width;
    vo->dheight = height;

    create_shm_pool(vo);

    // 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, &p->screen_osd_res);
    int window_w = MPMAX(0, p->screen_osd_res.ml + p->screen_osd_res.mr) + mp_rect_w(dst);
    int window_h = MPMAX(0, p->screen_osd_res.mt + p->screen_osd_res.mb) + mp_rect_h(dst);
    wp_viewport_set_destination(wl->viewport, lround(window_w / wl->scaling_factor),
                                lround(window_h / wl->scaling_factor));

    //now we restore pan for video viewport calculation
    vo->opts->pan_x = opts->pan_x;
    vo->opts->pan_y = opts->pan_y;
    vo_get_src_dst_rects(vo, &src, &dst, &p->screen_osd_res);
    wp_viewport_set_destination(wl->video_viewport, lround(mp_rect_w(dst) / wl->scaling_factor),
                                                    lround(mp_rect_h(dst) / wl->scaling_factor));
    wl_subsurface_set_position(wl->video_subsurface, lround(dst.x0 / wl->scaling_factor), lround(dst.y0 / wl->scaling_factor));
    wp_viewport_set_destination(wl->osd_viewport, lround(vo->dwidth / wl->scaling_factor),
                                                  lround(vo->dheight / wl->scaling_factor));
    wl_subsurface_set_position(wl->osd_subsurface, lround((0 - dst.x0) / wl->scaling_factor), lround((0 - dst.y0) / wl->scaling_factor));
    set_viewport_source(vo, src);

    mp_mutex_lock(&vo->params_mutex);
    vo->target_params->w = mp_rect_w(dst);
    vo->target_params->h = mp_rect_h(dst);
    vo->target_params->rotate = (vo->params->rotate % 90) * 90;
    vo->target_params->vflip = vo->params->vflip;
    mp_mutex_unlock(&vo->params_mutex);

    vo->want_redraw = true;
}

static bool draw_osd(struct vo *vo, struct mp_image *cur, double pts)
{
    struct priv *p = vo->priv;
    struct mp_osd_res *res = &p->screen_osd_res;
    bool draw = false;

    struct sub_bitmap_list *sbs = osd_render(vo->osd, *res, pts, 0, mp_draw_sub_formats);

    if (!sbs)
        return draw;

    struct mp_rect act_rc[1], mod_rc[64];
    int num_act_rc = 0, num_mod_rc = 0;

    if (!p->osd_cache)
        p->osd_cache = mp_draw_sub_alloc(p, vo->global);

    struct mp_image *osd = mp_draw_sub_overlay(p->osd_cache, sbs, act_rc,
                                               MP_ARRAY_SIZE(act_rc), &num_act_rc,
                                               mod_rc, MP_ARRAY_SIZE(mod_rc), &num_mod_rc);

    p->osd_surface_has_contents = num_act_rc > 0;

    if (!osd || !num_mod_rc)
        goto done;

    for (int n = 0; n < num_mod_rc; n++) {
        struct mp_rect rc = mod_rc[n];

        int rw = mp_rect_w(rc);
        int rh = mp_rect_h(rc);

        void *src = mp_image_pixel_ptr(osd, 0, rc.x0, rc.y0);
        void *dst = cur->planes[0] + rc.x0 * 4 + rc.y0 * cur->stride[0];

        memcpy_pic(dst, src, rw * 4, rh, cur->stride[0], osd->stride[0]);
    }

    draw = true;
done:
    talloc_free(sbs);
    return draw;
}

static bool draw_frame(struct vo *vo, struct vo_frame *frame)
{
    struct priv *p = vo->priv;
    struct vo_wayland_state *wl = vo->wl;
    struct buffer *buf;
    struct osd_buffer *osd_buf;
    double pts;

    if (!vo_wayland_check_visible(vo)) {
        if (frame->current)
            talloc_free(frame);
        return VO_FALSE;
    }

    if (p->destroy_buffers)
        destroy_buffers(vo);

    // Reuse the solid buffer so the osd can be visible
    if (p->force_window) {
        wp_viewport_set_source(wl->video_viewport, wl_fixed_from_int(-1),
                               wl_fixed_from_int(-1), wl_fixed_from_int(-1),
                               wl_fixed_from_int(-1));
        wl_surface_attach(wl->video_surface, p->solid_buffer, 0, 0);
        wl_surface_damage_buffer(wl->video_surface, 0, 0, 1, 1);
    }

    pts = frame->current ? frame->current->pts : 0;
    if (frame->current) {
        buf = buffer_get(vo, frame);
        vo_wayland_handle_color(wl, &p->target_params);

        if (buf && buf->frame) {
            struct mp_image *image = buf->frame->current;
            wl_surface_attach(wl->video_surface, buf->buffer, 0, 0);
            wl_surface_damage_buffer(wl->video_surface, 0, 0, image->w,
                                     image->h);

        }
    }

    osd_buf = osd_buffer_get(vo);
    if (osd_buf && osd_buf->buffer) {
        if (draw_osd(vo, &osd_buf->image, pts) && p->osd_surface_has_contents) {
            wl_surface_attach(wl->osd_surface, osd_buf->buffer, 0, 0);
            wl_surface_damage_buffer(wl->osd_surface, 0, 0, osd_buf->image.w,
                                     osd_buf->image.h);
            osd_buf->attached = true;
            p->osd_surface_is_mapped = true;
        } else if (!p->osd_surface_has_contents && p->osd_surface_is_mapped) {
            wl_surface_attach(wl->osd_surface, NULL, 0, 0);
            p->osd_surface_is_mapped = false;
        }
    }

    return VO_TRUE;
}

static void flip_page(struct vo *vo)
{
    struct vo_wayland_state *wl = vo->wl;

    wl_surface_commit(wl->osd_surface);
    wl_surface_commit(wl->video_surface);
    wl_surface_commit(wl->surface);

    if (wl->opts->wl_internal_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 int query_format(struct vo *vo, int format)
{
    return format == IMGFMT_DRMPRIME || format == IMGFMT_VAAPI;
}

static const int32_t transform_enum_lut[4][2] = {
    {0, 6}, {1, 5}, {2, 4}, {3, 7},
};

static int reconfig(struct vo *vo, struct mp_image *img)
{
    struct priv *p = vo->priv;

    switch (img->imgfmt) {
    case IMGFMT_VAAPI:
        p->hwdec_type = HWDEC_VAAPI;
        break;
    case IMGFMT_DRMPRIME:
        p->hwdec_type = HWDEC_DRMPRIME;
        break;
    default:
        p->hwdec_type = HWDEC_NONE;
    }

    if (p->hwdec_type == HWDEC_NONE) {
        MP_ERR(vo, "Format '%s' is not a valid hardware accelerated format!\n",
               mp_imgfmt_to_name(img->imgfmt));
        return VO_ERROR;
    }

    if (img->params.force_window) {
        p->force_window = true;
        goto done;
    }

    if (!drm_format_check(vo, img)) {
        MP_ERR(vo, "Unable to get drm format from hardware decoding!\n");
        return VO_ERROR;
    }

    if (!ra_compatible_format(p->ctx->ra, p->drm_format, p->drm_modifier)) {
        MP_ERR(vo, "Format '%s' with modifier '(%016" PRIx64 ")' is not supported by"
               " the compositor.\n", mp_tag_str(p->drm_format), p->drm_modifier);
        return VO_ERROR;
    }

    p->force_window = false;
done:
    if (!vo_wayland_reconfig(vo))
        return VO_ERROR;

    mp_mutex_lock(&vo->params_mutex);
    p->target_params = img->params;
    // Restore fallback layer parameters if available.
    mp_image_params_restore_dovi_mapping(&p->target_params);
    mp_image_params_guess_csp(&p->target_params);
    // Strip metadata that is not understood anyway.
    struct pl_hdr_metadata *hdr = &p->target_params.color.hdr;
    hdr->scene_max[0] = hdr->scene_max[1] = hdr->scene_max[2] = 0;
    hdr->scene_avg = hdr->max_pq_y = hdr->avg_pq_y = 0;
    vo->target_params = &p->target_params;
    mp_mutex_unlock(&vo->params_mutex);

    wl_surface_set_buffer_transform(vo->wl->video_surface,
        transform_enum_lut[img->params.rotate / 90][!!img->params.vflip]);

    // Immediately destroy all buffers if params change.
    destroy_buffers(vo);
    return 0;
}

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_RESET:
        p->destroy_buffers = true;
        return VO_TRUE;
    case VOCTRL_SET_PANSCAN:
        resize(vo);
        return VO_TRUE;
    }

    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;

    destroy_buffers(vo);
    destroy_osd_buffers(vo);
    if (p->osd_shm_pool)
        wl_shm_pool_destroy(p->osd_shm_pool);
    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);
    }

    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");
    wl_list_init(&p->buffer_list);
    wl_list_init(&p->osd_buffer_list);
    if (!p->ctx)
        goto err;

    mp_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->single_pixel_manager) {
        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 */
    } else {
        int width = 1;
        int height = 1;
        int stride = MP_ALIGN_UP(width * 4, MP_IMAGE_BYTE_ALIGN);
        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();
    p->hwdec_ctx = (struct ra_hwdec_ctx) {
        .log = p->log,
        .global = p->global,
        .ra_ctx = p->ctx,
    };

    // Initialize all possible hwdec drivers.
    ra_hwdec_ctx_init(&p->hwdec_ctx, vo->hwdec_devs, "vaapi", false);
    ra_hwdec_ctx_init(&p->hwdec_ctx, vo->hwdec_devs, "drmprime", false);

    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",
    .caps = VO_CAP_ROTATE90 |
            VO_CAP_FRAMEOWNER |
            VO_CAP_VFLIP |
            0x0,
    .preinit = preinit,
    .query_format = query_format,
    .reconfig2 = 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),
};
