/**
 * @file vaapi_x11.c
 * @brief VA API using X11 video output module for VLC media player
 */
/*****************************************************************************
 * Copyright © 2011, M2X BV
 * Author: Jean-Paul Saman
 *
 * This library 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.
 *
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 ****************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdlib.h>
#include <assert.h>

#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_picture.h>
#include <vlc_vout_display.h>
#include <vlc_vout_window.h>
#include <vlc_picture_pool.h>

#ifdef HAVE_AVCODEC_VAAPI

#ifdef HAVE_LIBAVCODEC_AVCODEC_H
#   include <libavcodec/avcodec.h>
#else
#   include <avcodec.h>
#endif

#include "avcodec.h"
#include "copy.h"

#include <vlc_xlib.h>
#include <libavcodec/vaapi.h>
#include <va/va.h>
#include <va/va_x11.h>
#include "va.h"
#include "vaapi.h"

#include <X11/Xutil.h>

static const vlc_fourcc_t va_subpicture_chromas[] = {
    VLC_CODEC_RGBA,
    0
};

struct vout_display_sys_t
{
    vlc_va_conn_t *conn;

    vout_window_t *embed;

    picture_pool_t *pool; /* picture pool */
    VAImageFormat sub_fmt; /* Subpicture format VA_FOURCC_RGBA */
    VAImageFormat img_fmt;

    unsigned int i_display_order;
    bool b_alpha;  /* whether subpictures support alpha */
    bool b_chroma; /* whether subpictures support chroma keying */
    bool visible;  /* whether to draw */
};

static picture_pool_t *Pool (vout_display_t *, unsigned);
static void Render (vout_display_t *, picture_t *, subpicture_t *);
static void DisplayPicture (vout_display_t *, picture_t *, subpicture_t *);
static int Control  (vout_display_t *, int, va_list);

/* */
static int VASubtitleFourCC(vout_display_t *vd)
{
    vout_display_sys_t *sys = (vout_display_sys_t *) vd->sys;

    int num = vaMaxNumSubpictureFormats(sys->conn->p_display);
    if (num <= 0)
        goto out_warning;

    VAImageFormat *p_fmt = calloc(num, sizeof(*p_fmt));
    unsigned int  *flags = calloc(num, sizeof(*flags));
    if (!p_fmt || !flags)
        goto out;

    unsigned int num_fmt = 0;
    VAStatus status = vaQuerySubpictureFormats(sys->conn->p_display,
                                               p_fmt, flags, &num_fmt);
    if (status != VA_STATUS_SUCCESS)
        goto out;

    for (unsigned int i = 0; i < num_fmt; i++)
    {
        VAImageFormat format = p_fmt[i];
        if (format.fourcc == VA_FOURCC_RGBA)
        {
            sys->b_alpha  = (flags[i] & VA_SUBPICTURE_GLOBAL_ALPHA);
            sys->b_chroma = (flags[i] & VA_SUBPICTURE_CHROMA_KEYING);
            memcpy((void*)&sys->sub_fmt, (void*)&format, sizeof(VAImageFormat));
            free(p_fmt);
            free(flags);
            return VLC_SUCCESS;
        }
    }

out:
    free(p_fmt);
    free(flags);

out_warning:
    msg_Warn(vd, "VAAP-X11 does not support fourcc RGBA subtitles");
    return VLC_EGENERIC;
}

static int FindVAFourCC(vout_display_t *vd)
{
    vout_display_sys_t *sys = (vout_display_sys_t *) vd->sys;

    /* Find and create a supported image chroma */
    int i_fmt_count = vaMaxNumImageFormats(sys->conn->p_display);
    VAImageFormat *p_fmt = calloc(i_fmt_count, sizeof(*p_fmt));
    if( !p_fmt )
        goto error;

    if( vaQueryImageFormats( sys->conn->p_display, p_fmt, &i_fmt_count ) )
    {
        free( p_fmt );
        goto error;
    }

    vlc_fourcc_t  i_chroma = 0;
    int32_t i_bits_per_pixel = 0;
    for( int i = 0; i < i_fmt_count; i++ )
    {
        if( p_fmt[i].fourcc == VA_FOURCC( 'Y', 'V', '1', '2' ) ||
            p_fmt[i].fourcc == VA_FOURCC( 'I', '4', '2', '0' ) ||
            p_fmt[i].fourcc == VA_FOURCC( 'N', 'V', '1', '2' ) )
        {
            memcpy((void*)&sys->img_fmt, (void*)&p_fmt[i], sizeof(VAImageFormat));
            i_chroma = VLC_CODEC_YV12;
            i_bits_per_pixel = p_fmt[i].bits_per_pixel;
            break;
        }
    }
    free( p_fmt );
    if( !i_chroma )
        goto error;

    vd->fmt.i_chroma = i_chroma;
    vd->fmt.i_bits_per_pixel = i_bits_per_pixel;
    return VLC_SUCCESS;

error:
    return VLC_EGENERIC;
}

static vout_window_t *MakeWindow (vout_display_t *vd)
{
    vout_window_cfg_t wnd_cfg;

    memset (&wnd_cfg, 0, sizeof (wnd_cfg));
    wnd_cfg.type = VOUT_WINDOW_TYPE_XID;
    wnd_cfg.x = var_InheritInteger (vd, "video-x");
    wnd_cfg.y = var_InheritInteger (vd, "video-y");
    wnd_cfg.width  = vd->cfg->display.width;
    wnd_cfg.height = vd->cfg->display.height;

    vout_window_t *wnd = vout_display_NewWindow (vd, &wnd_cfg);
    if (wnd == NULL)
        msg_Err (vd, "parent window not available");
    return wnd;
}

/**
 * Probe the X server.
 */
int OpenVaapiX11 (vlc_object_t *obj)
{
    vout_display_t *vd = (vout_display_t *) obj;

    if( !vlc_xlib_init( obj ) )
        return VLC_EGENERIC;

    vout_display_sys_t *sys = calloc(1, sizeof(*sys));
    if (unlikely(sys == NULL))
        return VLC_ENOMEM;

    vd->sys = sys;

    sys->conn = NULL;
    sys->pool = NULL;

    sys->b_alpha = false;
    sys->b_chroma = false;
    sys->visible = false;
    sys->i_display_order = 0;

    sys->embed = MakeWindow(vd);
    if (unlikely(sys->embed == NULL))
        goto error;

    /* Create a VA display */
    sys->conn = vlc_va_Initialize(sys->embed->display.x11);
    if (!sys->conn)
        goto error;

    if (FindVAFourCC(vd) != VLC_SUCCESS)
        goto error;

    msg_Dbg(vd, "using VAAPI X11 window (version %d.%d)",
            sys->conn->i_version_major, sys->conn->i_version_minor);

    /* */
    vout_display_info_t info = vd->info;
    info.is_slow = false;
    info.has_pictures_invalid = false;
    info.has_event_thread = false;
    info.has_hide_mouse = false;

    /* Setup vout_display_t once everything is fine */
    if (VASubtitleFourCC(vd) == VLC_SUCCESS)
    {
        info.subpicture_chromas = va_subpicture_chromas;
        vd->prepare = Render;
    }
    else
    {
        vd->prepare = NULL;
        info.subpicture_chromas = NULL;
    }

    vd->pool = Pool;
    vd->display = DisplayPicture;
    vd->control = Control;
    vd->manage = NULL; //Manage;
    vd->info = info;

    /* */
    bool is_fullscreen = vd->cfg->is_fullscreen;
    if (is_fullscreen && vout_window_SetFullScreen (sys->embed, true))
        is_fullscreen = false;
    vout_display_SendEventFullscreen (vd, is_fullscreen);
    vout_display_SendEventDisplaySize (vd, vd->cfg->display.width, vd->cfg->display.height, is_fullscreen);

    return VLC_SUCCESS;

error:
    CloseVaapiX11(obj);
    return VLC_EGENERIC;
}

void CloseVaapiX11 (vlc_object_t *obj)
{
    vout_display_t *vd = (vout_display_t *) obj;
    vout_display_sys_t *sys = (vout_display_sys_t *) vd->sys;

    if (sys->conn)
    {
        if (sys->conn->p_display_x11)
        {
            XFlush(sys->conn->p_display_x11);
            XSync(sys->conn->p_display_x11, False);
        }
    }
    if (sys->embed)
        vout_display_DeleteWindow (vd, sys->embed);
    if (sys->conn)
        vlc_va_Terminate(sys->conn);

    if (sys->pool)
    {
        picture_pool_Delete(sys->pool);
        sys->pool = NULL;
    }

    free(vd->sys);
}

static int Control (vout_display_t *vd, int query, va_list ap)
{
    vout_display_sys_t *sys = vd->sys;

    switch (query)
    {
    case VOUT_DISPLAY_CHANGE_FULLSCREEN:
    {
        const vout_display_cfg_t *c = va_arg (ap, const vout_display_cfg_t *);
        return vout_window_SetFullScreen (sys->embed, c->is_fullscreen);
    }

    case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
    {
        unsigned state = va_arg (ap, unsigned);
        return vout_window_SetState (sys->embed, state);
    }

    case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
    case VOUT_DISPLAY_CHANGE_ZOOM:
    case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
    case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
    case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
    {
        /* I am not sure it is always necessary, but it is way simpler ... */
        /* FIXME: adjust picture size */
        const vout_display_cfg_t *cfg;
        const video_format_t *source;
        bool is_forced = false;

        if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT
         || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP)
        {
            source = (const video_format_t *)va_arg (ap, const video_format_t *);
            cfg = vd->cfg;
        }
        else
        {
            source = &vd->source;
            cfg = (const vout_display_cfg_t*)va_arg (ap, const vout_display_cfg_t *);
            if (query == VOUT_DISPLAY_CHANGE_DISPLAY_SIZE)
                is_forced = (bool)va_arg (ap, int);
        }

        /* */
        if (query == VOUT_DISPLAY_CHANGE_DISPLAY_SIZE
         && is_forced
         && (cfg->display.width  != vd->cfg->display.width
           ||cfg->display.height != vd->cfg->display.height)
         && vout_window_SetSize (sys->embed,
                                 cfg->display.width, cfg->display.height))
            return VLC_EGENERIC;

        return VLC_SUCCESS;
    }
    case VOUT_DISPLAY_RESET_PICTURES:
    {
        assert(0);
        return VLC_SUCCESS;
    }

    /* Hide the mouse. It will be send when
     * vout_display_t::info.b_hide_mouse is false */
    case VOUT_DISPLAY_HIDE_MOUSE:
        /* FIXME */
        return VLC_SUCCESS;

    default:
        msg_Err (vd, "Unknown request in XCB vout display");
        return VLC_EGENERIC;
    }
}

static VASubpictureID SubpictureCreate(vout_display_t *vd, const subpicture_t *subpicture,
                const subpicture_region_t *region, vlc_va_subpicture_t *vasubpicture)
{
    vout_display_sys_t *sys = vd->sys;

    VAStatus status;
    VASubpictureID subpic = VA_INVALID_ID;

    status = vaCreateSubpicture(sys->conn->p_display, vasubpicture->image.image_id, &subpic);
    if (status != VA_STATUS_SUCCESS)
        return VA_INVALID_ID;

    if (sys->b_alpha)
    {
        assert(!sys->b_chroma);

        float global_alpha = (float)(subpicture->i_alpha * region->i_alpha / 255) / 255;
        status = vaSetSubpictureGlobalAlpha(sys->conn->p_display, subpic, global_alpha);
        if (status != VA_STATUS_SUCCESS)
            msg_Err(vd, "Failed applying alpha value to subpicture");
    }

    if (sys->b_chroma)
    {
        assert(!sys->b_alpha);

        /* chromakey values */
        const unsigned int min = 0x0; /* ? */
        const unsigned int max = 0x00ff0000; /* ? */
        const unsigned int mask = (region->p_picture->format.i_rmask << 24) |
                                  (region->p_picture->format.i_gmask << 16) |
                                  (region->p_picture->format.i_bmask <<  8) | 0;

        status = vaSetSubpictureChromakey(sys->conn->p_display, subpic,
                                          min, max, mask);
        if (status != VA_STATUS_SUCCESS)
            msg_Err(vd, "Failed applying chroma valuesto subpicture");
    }

    int flags = sys->b_alpha ? VA_SUBPICTURE_GLOBAL_ALPHA :
                    (sys->b_chroma ? VA_SUBPICTURE_CHROMA_KEYING : 0);

    /* subpicture positioning */
    vout_display_place_t video;
    vout_display_PlacePicture (&video, &vd->source, vd->cfg, false);

    unsigned int src_left   = 0;
    unsigned int src_right  = src_left + region->fmt.i_visible_width;
    unsigned int src_top    = 0;
    unsigned int src_bottom = src_top  + region->fmt.i_visible_height;

    const float scale_w = (float)(video.width  - video.x) / subpicture->i_original_picture_width;
    const float scale_h = (float)(video.height - video.y) / subpicture->i_original_picture_height;

    unsigned int dst_left   = video.x  + scale_w * region->i_x;
    unsigned int dst_right  = dst_left + scale_w * region->fmt.i_visible_width;
    unsigned int dst_top    = video.y  + scale_h * region->i_y;
    unsigned int dst_bottom = dst_top  + scale_h * region->fmt.i_visible_height;

    /* Associate subpicture with surface */
    status = vaAssociateSubpicture(sys->conn->p_display, subpic,
                                   &vasubpicture->i_surface, 1, /* array of surfaces */
                                   /* src rectangle */
                                   src_left, src_top, src_right, src_bottom,
                                   /* dest rectangle */
                                   ((video.width - dst_right) / 2), dst_top,
                                   dst_right - dst_left, dst_bottom - dst_top,
                                   flags);
    if (status != VA_STATUS_SUCCESS)
    {
        vaDestroySubpicture(sys->conn->p_display, subpic);
        return VA_INVALID_ID;
    }
    return subpic;
}

static void SubpictureDestroy(vout_display_t *vd, vlc_va_subpicture_t *subpicture, const int i_sub)
{
    vout_display_sys_t *sys = vd->sys;

    VAStatus status;
    for (int i = 0; i < i_sub; i++)
    {
        if ((subpicture[i].i_surface != VA_INVALID_SURFACE) &&
            (subpicture[i].i_id != VA_INVALID_ID))
        {
            status = vaDeassociateSubpicture(sys->conn->p_display,
                                             subpicture[i].i_id,
                                             &subpicture[i].i_surface, 1);
            if (status != VA_STATUS_SUCCESS)
                msg_Err(vd, "Failed unlinking subpicture from surface (%d)", status);
        }

        if (subpicture[i].i_id != VA_INVALID_ID)
        {
            status = vaDestroySubpicture(sys->conn->p_display,
                                         subpicture[i].i_id);
            if (status != VA_STATUS_SUCCESS)
                msg_Err(vd, "Failed destroying subpicture");
        }

        if (subpicture[i].image.image_id != VA_INVALID_ID)
        {
            status = vaDestroyImage(sys->conn->p_display,
                                    subpicture[i].image.image_id);
            if (status != VA_STATUS_SUCCESS)
                msg_Err(vd, "Failed destroying subpicture image");
        }

        subpicture[i].i_surface = VA_INVALID_SURFACE;
        subpicture[i].image.image_id = VA_INVALID_ID;
        subpicture[i].image.buf = VA_INVALID_ID;
        subpicture[i].i_id = VA_INVALID_ID;
    }

    /* */
    free(subpicture);
}

static int CopyPictureToVAImage(vout_display_t *vd, picture_t *pic,
                                VAImage *image, VAImageFormat *fmt)
{
    vout_display_sys_t *sys = vd->sys;

    VAStatus status;
    status = vaCreateImage(sys->conn->p_display, fmt,
                           pic->format.i_width, pic->format.i_height, image);
    if (status != VA_STATUS_SUCCESS)
        return VLC_EGENERIC;

    /* Sanity checks */
    if ((image->format.fourcc != VA_FOURCC_RGBA) ||
        (image->num_planes != (unsigned int) pic->i_planes) ||
        (image->height != pic->format.i_height) ||
        (image->width != pic->format.i_width) ||
        (image->format.bits_per_pixel != pic->format.i_bits_per_pixel))
    {
        msg_Err(vd, "subpicture region: planes %d/%d, depth %d, width %d/%d, height %d/%d, bpp %d/%d",
                 image->num_planes, pic->i_planes,
                 image->format.depth,
                 image->width, pic->format.i_width,
                 image->height, pic->format.i_height,
                 image->format.bits_per_pixel, pic->format.i_bits_per_pixel);
        goto out_va_free;
    }

    void *p_base;
    status = vaMapBuffer(sys->conn->p_display, image->buf, &p_base);
    if (status != VA_STATUS_SUCCESS)
    {
        msg_Err(vd, "Failed mapping image for copying (%d)", status);
        goto out_va_free;
    }

    for (unsigned int i_plane = 0; i_plane < image->num_planes; i_plane++)
    {
        /* Does the pitch match? Then just copy it directly */
        if (image->pitches[i_plane] == (unsigned int)pic->p[i_plane].i_pitch)
        {
            vlc_memcpy((uint8_t*)(((uint8_t*)p_base) + image->offsets[i_plane]),
                       pic->p[i_plane].p_pixels,
                       pic->p[i_plane].i_pitch * pic->p[i_plane].i_visible_lines);
        }
        else
        {
            /* proceed line by line */
            uint8_t *p_in  = pic->p[i_plane].p_pixels;
            uint8_t *p_out = (uint8_t *)(((uint8_t*)p_base) + image->offsets[i_plane]);

            int i_line = pic->format.i_y_offset * pic->p[i_plane].i_visible_pitch;
            for (; i_line < pic->p[i_plane].i_visible_lines; i_line++)
            {
                /* NOTE: image->pitch[i_plane] includes image->offset[i_plane], thus
                 * visible_pitch = image->pitch[i_plane] - image->offset[i_plane] */
                size_t i_size = __MIN(image->pitches[i_plane],
                                      (unsigned int)pic->p[i_plane].i_pitch);
                vlc_memcpy(&p_out[i_line * image->pitches[i_plane]], &p_in[i_line * pic->p[i_plane].i_pitch], i_size);
            }
        }
    }

    status = vaUnmapBuffer(sys->conn->p_display, image->buf);
    if (status != VA_STATUS_SUCCESS)
    {
        msg_Err(vd, "Failed unmapping image for copying (%d)(buf id: %d)",
                status, image->buf);
        goto out_va_free;
    }

    return VLC_SUCCESS;

out_va_free:
    vaDestroyImage(sys->conn->p_display, image->image_id);
    image->image_id = VA_INVALID_ID;
    return VLC_EGENERIC;
}

static VASurfaceID CopyPictureToVASurface(vout_display_t *vd, picture_t *picture, VAImage *image)
{
    vout_display_sys_t *sys = vd->sys;
    VASurfaceID surface = VA_INVALID_SURFACE;
    VAStatus status;

    status = vaCreateSurfaces(sys->conn->p_display,
                             picture->format.i_visible_width,
                             picture->format.i_visible_height,
                             VA_RT_FORMAT_YUV420,
                             1, &surface);
    if (status != VA_STATUS_SUCCESS)
    {
        msg_Err(vd, "Failed to create display surface");
        return VA_INVALID_ID;
    }
    image->image_id = VA_INVALID_ID;
    int err = CopyPictureToVAImage(vd, picture, image, &sys->img_fmt);
    if (err != VLC_SUCCESS)
        return VA_INVALID_ID;

    vout_display_place_t place;
    vout_display_PlacePicture (&place, &vd->source, vd->cfg, false);

    status = vaPutImage(sys->conn->p_display,
                         surface,
                         image->image_id,
                         picture->format.i_x_offset, picture->format.i_y_offset,
                         picture->format.i_width, picture->format.i_height,
                         place.x, place.y,
                         place.width, place.height);
    if (status != VA_STATUS_SUCCESS)
        return VA_INVALID_ID;

    return surface;
}

static picture_sys_t *RenderImportPicture(vout_display_t *vd, picture_t *picture)
{
    vout_display_sys_t *sys = vd->sys;

    picture_sys_t *pic_sys = calloc(1, sizeof(picture_sys_t));
    if (!pic_sys)
        return NULL;

    pic_sys->b_own_vout = true;
    pic_sys->surface = calloc(1, sizeof(vlc_va_surface_t));
    if (!pic_sys->surface)
    {
        free(pic_sys);
        return NULL;
    }

    VAImage image;
    image.buf = VA_INVALID_ID;
    image.image_id = VA_INVALID_ID;
    pic_sys->surface->i_id = CopyPictureToVASurface(vd, picture, &image);
    if (pic_sys->surface->i_id == VA_INVALID_SURFACE)
    {
        if (image.image_id != VA_INVALID_ID)
            vaDestroyImage(sys->conn->p_display, image.image_id);
        free(pic_sys->surface);
        free(pic_sys);
        return NULL;
    }

    if (image.image_id != VA_INVALID_ID)
        vaDestroyImage(sys->conn->p_display, image.image_id);

    pic_sys->surface->i_refcount = 1;
    return pic_sys;
}

static int RenderDirectSubpicture(vout_display_t *vd, picture_t *picture, subpicture_t *subpicture, unsigned int i_region)
{
    vout_display_sys_t *sys = vd->sys;

    assert(picture->p_sys->subpicture == NULL);

    vlc_va_subpicture_t *vasubpic = (vlc_va_subpicture_t*) calloc(i_region, sizeof(vlc_va_subpicture_t));
    if (!vasubpic)
        return VLC_EGENERIC;

    unsigned int i_sub = 0;
    subpicture_region_t *region;
    for (region = subpicture->p_region; region != NULL; region = region->p_next)
    {
        /* Skip non-RGBA regions for now */
        if (region->fmt.i_chroma != VLC_CODEC_RGBA)
            continue;

        /* Copy pixels from region into VAImage */
        VAImage *image = &vasubpic[i_sub].image;
        image->image_id = VA_INVALID_ID;
        image->buf = VA_INVALID_ID;
        int err = CopyPictureToVAImage(vd, region->p_picture, image, &sys->sub_fmt);
        if (err != VLC_SUCCESS)
        {
            msg_Err(vd, "Failed to copy region into vaImage");
            free(vasubpic);
            return VLC_EGENERIC;
        }

        vasubpic[i_sub].image.format.alpha_mask = 0xFFFFFF00;
        vasubpic[i_sub].image.format.red_mask = region->p_picture->format.i_rmask;
        vasubpic[i_sub].image.format.blue_mask = region->p_picture->format.i_bmask;
        vasubpic[i_sub].image.format.green_mask = region->p_picture->format.i_gmask;

        vasubpic[i_sub].i_surface = picture->p_sys->surface->i_id;

        /* Create Subpicture */
        vasubpic[i_sub].i_id = SubpictureCreate(vd, subpicture, region,
                                                    &vasubpic[i_sub]);
        if (vasubpic[i_sub].i_id == VA_INVALID_ID)
        {
            vaDestroyImage(sys->conn->p_display, vasubpic[i_sub].image.image_id);
            free(vasubpic);
            return VLC_EGENERIC;
        }

        i_sub++;
    }

    /* remember what was created, so it can be released in DisplayPicture() */
    picture->p_sys->subpicture = vasubpic;
    picture->p_sys->i_sub = i_sub;

    return VLC_SUCCESS;
}

static void PictureRelease(picture_t *picture)
{
    if (picture->i_refcount > 0)
        picture->i_refcount--;
    if (picture->p_sys)
    {
        vlc_va_conn_t *conn = vlc_va_get_conn();
        vlc_va_subpicture_t *subpicture = picture->p_sys->subpicture;

        VAStatus status;
        const int i_sub = picture->p_sys->i_sub;
        for (int i = 0; i < i_sub; i++)
        {
            if ((subpicture[i].i_surface != VA_INVALID_SURFACE) &&
                (subpicture[i].i_id != VA_INVALID_ID))
            {
                status = vaDeassociateSubpicture(conn->p_display,
                            subpicture[i].i_id, &subpicture[i].i_surface, 1);
                assert(status == VA_STATUS_SUCCESS);
            }

            if (subpicture[i].i_id != VA_INVALID_ID)
            {
                status = vaDestroySubpicture(conn->p_display, subpicture[i].i_id);
                assert(status == VA_STATUS_SUCCESS);
            }

            if (subpicture[i].image.image_id != VA_INVALID_ID)
            {
                status = vaDestroyImage(conn->p_display,
                                        subpicture[i].image.image_id);
                assert(status == VA_STATUS_SUCCESS);
            }
        }
        free(picture->p_sys->subpicture);

        /* */
        picture->p_sys->i_sub = 0;
        picture->p_sys->surface = NULL;
        picture->p_sys->subpicture = NULL;
    }
    free(picture->p_sys);
    picture->p_sys = NULL;
}

static void PictureReleaseVoutOwned(picture_t *picture)
{
    if (!picture->p_sys->b_own_vout)
        abort();

    if (picture->i_refcount > 0)
        picture->i_refcount--;

    vlc_va_surface_t *surface = picture->p_sys->surface;
#if 0
    /* FIXME: pictures discarded by core will leak VASurfaces */
    if (surface->i_id != VA_INVALID_SURFACE)
        vaDestroySurfaces(sys->conn->p_display, &surface->i_id, 1);
#endif
    free(surface);
    free(picture->p_sys);
    picture->p_sys = NULL;
}

static void Render(vout_display_t *vd, picture_t *picture, subpicture_t *subpicture)
{
    if (!subpicture)
        return;

    if (subpicture->b_ephemer)
        return;

    /* Cleanup for reclaimed surface */
    if (picture->p_sys &&
        picture->p_sys->subpicture &&
        (picture->format.i_chroma == VLC_CODEC_VAAPI_SURFACE))
    {
        SubpictureDestroy(vd, picture->p_sys->subpicture, picture->p_sys->i_sub);
        picture->p_sys->subpicture = NULL;
        picture->p_sys->i_sub = 0;
    }

    if (picture->format.i_chroma != VLC_CODEC_VAAPI_SURFACE)
    {
        assert(picture->p_sys);
        picture->p_sys = RenderImportPicture(vd, picture);
        if (!picture->p_sys)
            return;
        assert(picture->p_sys->surface);
        assert(picture->pf_release);
        picture->pf_release = PictureReleaseVoutOwned;
    }

    if (!picture->p_sys->surface)
        return;

    /* count RGBA subpicture regions */
    int i_region = 0;

    subpicture_region_t *region = NULL;
    for (region = subpicture->p_region; region != NULL; region = region->p_next)
    {
        /* Skip non-RGBA regions for now */
        if (region->fmt.i_chroma == VLC_CODEC_RGBA)
        {
            i_region++;
        }
    }

    if ((i_region > 0) &&
        (RenderDirectSubpicture(vd, picture, subpicture, i_region) != VLC_SUCCESS))
        msg_Err(vd, "failed rendering subtitles");
}

static void ReleaseSurface(vlc_va_surface_t *surface)
{
    if (surface->i_refcount > 0)
        surface->i_refcount--;
}

static void DisplayVASurface(vout_display_t *vd, VASurfaceID surface, picture_t *picture)
{
    vout_display_sys_t *sys = vd->sys;

    vout_display_place_t place;
    vout_display_PlacePicture (&place, &vd->source, vd->cfg, false);

    VAStatus status;
    status = vaPutSurface(sys->conn->p_display, surface, sys->embed->handle.xid,
                          picture->format.i_x_offset, picture->format.i_y_offset,
                          picture->format.i_visible_width, picture->format.i_visible_height,
                          place.x, place.y, place.width, place.height,
                          NULL, 0, /* clipping */
                          /* NOTE: vaCreateContext() specifies VA_PROGRESSIVE */
                          picture->b_progressive ? VA_FRAME_PICTURE :
                               (picture->b_top_field_first ? VA_TOP_FIELD : VA_BOTTOM_FIELD));

    if (status != VA_STATUS_SUCCESS)
        msg_Err(vd, "failed displaying picture (id=%d) %d", surface, status);
}

static void DisplayPicture(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
{
    vout_display_sys_t *sys = vd->sys;

    if (pic->p_sys == NULL)
    {
        msg_Err(vd, "Discarding picture without picture_sys_t information");
        if (subpicture)
            subpicture_Delete(subpicture);
        picture_Release(pic);
        return;
    }

    if (pic->p_sys->surface == NULL)
    {
        assert(!pic->p_sys->b_own_vout);
        msg_Err(vd, "Discarding picture without surface information");
        if (pic->p_sys->subpicture)
            SubpictureDestroy(vd, pic->p_sys->subpicture, pic->p_sys->i_sub);
        if (subpicture)
            subpicture_Delete(subpicture);
        pic->p_sys->subpicture = NULL;
        pic->p_sys->i_sub = 0;
        picture_Release(pic);
        return;
    }

    unsigned int i_order = (sys->i_display_order > 0) ?
        sys->i_display_order++ : pic->p_sys->surface->i_order;
    if ((i_order != pic->p_sys->surface->i_order) &&
        (pic->format.i_chroma == VLC_CODEC_VAAPI_SURFACE))
    {
        /* Reset picture */
        msg_Err(vd, "Reclaimed picture - id=%d, order=%d, refcount=%d (%p)",
                pic->p_sys->surface->i_id, pic->p_sys->surface->i_order,
                pic->p_sys->surface->i_refcount, pic);
        if (pic->p_sys->surface->i_refcount > 1)
            pic->p_sys->surface->i_refcount = 1;

        goto cleanup;
    }

    DisplayVASurface(vd, pic->p_sys->surface->i_id, pic);

cleanup:
    if (pic->p_sys->subpicture)
        SubpictureDestroy(vd, pic->p_sys->subpicture, pic->p_sys->i_sub);

    if (subpicture)
        subpicture_Delete(subpicture);

    if (pic->p_sys->b_own_vout)
    {
        if (pic->p_sys->surface->i_id != VA_INVALID_SURFACE)
            vaDestroySurfaces(sys->conn->p_display, &pic->p_sys->surface->i_id, 1);
    }

    ReleaseSurface(pic->p_sys->surface);

    if (!pic->p_sys->b_own_vout)
        pic->p_sys->surface = NULL;
    pic->p_sys->subpicture = NULL;
    pic->p_sys->i_sub = 0;

    picture_Release(pic);
}

/**
 * Return a direct buffer
 */
static picture_pool_t *Pool (vout_display_t *vd, unsigned requested_count)
{
    vout_display_sys_t *sys = vd->sys;

    assert(requested_count > 0);
    if (sys->pool)
        return sys->pool;

    picture_t **pic_array = (picture_t **) calloc(requested_count, sizeof(picture_t *));
    if (!pic_array)
        return NULL;

    unsigned int count;
    for (count = 0; count < requested_count; count++)
    {
        picture_t *pic = (picture_t*) picture_NewFromFormat(&vd->fmt);
        if (!pic)
            break;
        assert(pic->p_sys == NULL);
        picture_sys_t *sys = (picture_sys_t *) malloc(sizeof(picture_sys_t));
        if (!sys)
        {
            picture_Release(pic);
            break;
        }
        pic->format.i_chroma = VLC_CODEC_VAAPI_SURFACE;

        sys->b_own_vout = false;
        sys->subpicture = NULL;
        sys->surface = NULL;
        sys->i_sub = 0;

        pic_array[count] = pic;
        pic_array[count]->b_progressive = true;
        pic_array[count]->p_sys = sys;
        pic_array[count]->pf_release = PictureRelease;
    }

    if (count == 0)
        goto error;

    sys->pool = picture_pool_New(count, pic_array);
    if (!sys->pool)
        goto error;

    free(pic_array);
    return sys->pool;

error:
    /* Cleanup on error */
    for (unsigned int i = 0; i < count; i++)
    {
        picture_t *pic = pic_array[i];
        if (pic)
            picture_Release(pic);
    }
    free(pic_array);
    return NULL;
}

#endif /* HAVE_AVCODEC_VAAPI */
