/**
 * @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_es.h>
#include <vlc_picture.h>
#include <vlc_vout_display.h>
#include <vlc_vout_window.h>
#include <vlc_picture_pool.h>

#include <vlc_arrays.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>

/* define for extra debugging */
#undef VAAPI_DEBUG

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 img_fmt;
    VAImageFormat sub_fmt; /* Subpicture format VA_FOURCC_RGBA */
    int           sflags;  /* Subtitle format flags */

    struct {
        unsigned int   i_cache;
        mtime_t        i_start;
        mtime_t        i_stop;
        subpicture_t  *prev_subpicture;
    } render;
    struct {
        unsigned int  i_cache;
    } display;

    vlc_mutex_t   cache_lock;
    vlc_array_t   cache;  /* array of subpicture_subpicture_cache_t */

    unsigned int i_display_order;
    bool visible;  /* whether to draw */

    /* */
    Window window;
};

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);

/* cache management */
static unsigned int cache_counter = 0;

typedef struct
{
  VASubpictureID i_id;
  VAImage        image;
} vasubpicture_cache_t;

typedef struct
{
    unsigned int  i_id;
    mtime_t       i_start; /* start display date */
    mtime_t       i_stop;  /* stop display date */
    vlc_array_t   subpictures;
} subpicture_cache_t;

static subpicture_cache_t *cache_new(void)
{
    subpicture_cache_t *cache = malloc(sizeof(subpicture_cache_t));
    if (cache)
    {
        cache->i_id = cache_counter++;
        if (cache_counter == VA_INVALID_ID)
            cache_counter = 0;
        vlc_array_init(&cache->subpictures);
    }
    return cache;
}

static void cache_destroy(subpicture_cache_t *cache)
{
    assert(cache);
    assert(vlc_array_count(&cache->subpictures) == 0);

    vlc_array_clear(&cache->subpictures);
    free(cache);
}

static subpicture_cache_t *cache_find(vlc_array_t *cache, const unsigned int id)
{
    const int count = vlc_array_count(cache);
    for (int i = 0; i < count; i++)
    {
        subpicture_cache_t *c = (subpicture_cache_t *) vlc_array_item_at_index(cache, i);
        if (c)
        {
            if (c->i_id == id)
                return c;
        }
    }
    return NULL;
}

static vasubpicture_cache_t *subpicture_cache_new( void )
{
    vasubpicture_cache_t *cache = malloc(sizeof(vasubpicture_cache_t));
    if (cache)
    {
        cache->i_id = VA_INVALID_ID;
        cache->image.buf = VA_INVALID_ID;
        cache->image.image_id = VA_INVALID_ID;
    }
    return cache;
}

static void subpicture_cache_destroy(vasubpicture_cache_t *cache)
{
    assert(cache);
    assert(cache->i_id == VA_INVALID_ID);
    assert(cache->image.buf == VA_INVALID_ID);
    assert(cache->image.image_id == VA_INVALID_ID);

    free(cache);
}

/* Clear subpicture cache for this surface and
 * release allocated VA API resources */
static void SubpictureCacheClean(vout_display_t *vd, vlc_va_conn_t *conn, subpicture_cache_t *cache)
{
#ifdef VAAPI_DEBUG
    msg_Dbg(vd, "subpictureCacheClean cache %d", cache->i_id);
#else
    VLC_UNUSED(vd);
#endif
    const int num = vlc_array_count(&cache->subpictures);
    for (int i = num - 1; i >= 0; i--)
    {
        vasubpicture_cache_t *subpic;
        subpic = (vasubpicture_cache_t *)vlc_array_item_at_index(&cache->subpictures, i);
        if (!subpic)
            continue;

        vlc_array_remove(&cache->subpictures, i);
#ifdef VAAPI_DEBUG
        msg_Dbg(vd, "- %d: sub_id %d, image_id %d",
                 i, subpic->i_id, subpic->image.image_id);
#endif
        assert(subpic->i_id != VA_INVALID_ID);
        assert(subpic->image.image_id != VA_INVALID_ID);

        vaDestroySubpicture(conn->p_display, subpic->i_id);
        vaDestroyImage(conn->p_display, subpic->image.image_id);

        subpic->i_id = VA_INVALID_ID;
        subpic->image.buf = VA_INVALID_ID;
        subpic->image.image_id = VA_INVALID_ID;

        subpicture_cache_destroy(subpic);
        subpic = NULL;
    }
}

/* VA API support functions */
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->sflags = flags[i];
            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;

    /* FIXME: Correct vout_window_t display size
     * looks like it assumes fullscreen for
     * vout_display_PlacePicture() to work as expected. */
    unsigned int width, height;
    int screen = DefaultScreen(vd->sys->conn->p_display_x11);
    width = DisplayWidth(vd->sys->conn->p_display_x11, screen);
    height = DisplayHeight(vd->sys->conn->p_display_x11, screen);

    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  = width;
    wnd_cfg.height = height;

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

static int GetWindowSize(vout_display_t *vd, unsigned int *pi_width, unsigned int *pi_height)
{
    vout_display_sys_t *sys = vd->sys;
    Status status;
    Window root_window;

    int x, y;
    unsigned int bw, depth;
    status = XGetGeometry(sys->conn->p_display_x11,
                          sys->embed->handle.xid,
                          &root_window,
                          &x, &y, pi_width, pi_height,
                          &bw, &depth);
    if (status == 0)
    {
        msg_Err(vd, "failed getting geometry");
        return VLC_EGENERIC;
    }

    return VLC_SUCCESS;
}

/**
 * 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;

    vlc_array_init(&sys->cache);
    vlc_mutex_init(&sys->cache_lock);

    sys->render.prev_subpicture = NULL;
    sys->render.i_cache  = VA_INVALID_ID;
    sys->display.i_cache = VA_INVALID_ID;

    sys->visible = false;

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

    sys->embed = MakeWindow(vd);
    if (unlikely(sys->embed == NULL))
        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);
    unsigned int width, height;
    if (GetWindowSize(vd, &width, &height) == VLC_SUCCESS)
    {
        vout_window_SetSize (sys->embed, width, height);
        vout_display_SendEventDisplaySize (vd, width, 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 && 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);

    /* Cleanup cache */
    vlc_mutex_destroy(&sys->cache_lock);
    const int count = vlc_array_count(&sys->cache);
    for (int i = count - 1; i >= 0; i--)
    {
        subpicture_cache_t *cache;
        cache = (subpicture_cache_t*) vlc_array_item_at_index(&sys->cache, i);
        if (!cache)
            continue;

        vlc_array_remove(&sys->cache, i);

        SubpictureCacheClean(vd, sys->conn, cache);
        cache_destroy(cache);
        cache = NULL;
    }
    vlc_array_clear(&sys->cache);

    /* */
    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:
    {
        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, VAImage *image, const int flags)
{
    vout_display_sys_t *sys = vd->sys;

    VAStatus status;
    VASubpictureID sub_id = VA_INVALID_ID;

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

    bool b_alpha  = (flags & VA_SUBPICTURE_GLOBAL_ALPHA);
    bool b_chroma = (flags & VA_SUBPICTURE_CHROMA_KEYING);

    if (b_alpha)
    {
        assert(!b_chroma);

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

    if (b_chroma)
    {
        assert(!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, sub_id,
                                          min, max, mask);
        if (status != VA_STATUS_SUCCESS)
            msg_Err(vd, "failed applying chroma valuesto subpicture");
    }

    return sub_id;
}

static int SubpictureLink(vout_display_t *vd, VASurfaceID surface_id,
                          const subpicture_t *subpicture, const subpicture_region_t *region,
                          VASubpictureID vasub_id, const int flags)
{
    vout_display_sys_t *sys = vd->sys;

    assert(surface_id != VA_INVALID_ID);
    assert(vasub_id != VA_INVALID_ID);

    /* 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 */
    VAStatus status;
    status = vaAssociateSubpicture(sys->conn->p_display, vasub_id,
                                   &surface_id, 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)
        return VLC_EGENERIC;

    return VLC_SUCCESS;
}

static VAStatus SubpictureUnlink( vlc_va_conn_t *p_connection, VASurfaceID surface_id,
                                  VASubpictureID sub_id )
{
    VAStatus status = VA_STATUS_ERROR_UNKNOWN;
    if( (surface_id != VA_INVALID_SURFACE) &&
        (sub_id != VA_INVALID_ID) )
    {
        status = vaDeassociateSubpicture( p_connection->p_display, sub_id,
                                          &surface_id, 1 );
    }
    return status;
}

static void CopyPalette(vout_display_t *vd, VAImage *image, video_palette_t *p_palette)
{
    vout_display_sys_t *sys = vd->sys;

    /* vaSetImagePalette
     * pointer to an array holding the palette data.  The size of the array is
     * num_palette_entries * entry_bytes in size.  The order of the components
     * in the palette is described by the component_order in VAImage struct
     */
    uint8_t *palette = (uint8_t *) calloc(p_palette->i_entries, 4 * sizeof(uint8_t));
    if (!palette)
        return;

#define RED   0
#define BLUE  1
#define GREEN 2
#define ALPHA 3
    int entries;
    for (entries = 0; entries < p_palette->i_entries; entries++)
    {
        palette[RED]   = p_palette->palette[entries][RED];
        palette[BLUE]  = p_palette->palette[entries][BLUE];
        palette[GREEN] = p_palette->palette[entries][GREEN];
        palette[ALPHA] = p_palette->palette[entries][ALPHA];

        palette = palette + 4;
    }

#undef RED
#undef BLUE
#undef GREEN
#undef ALPHA

    image->num_palette_entries = entries;
    image->entry_bytes = 4; /* RGBA has 4 components */

    VAStatus status = vaSetImagePalette(sys->conn->p_display,
                             image->image_id, (unsigned char *)palette);
    if (status != VA_STATUS_SUCCESS)
        msg_Err(vd, "failed setting palette. (%d)", status);
}

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->image_id == VA_INVALID_ID) ||
        (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;
    }
    assert(image->image_id != VA_INVALID_ID);

    memcpy(image->component_order, "RGBA", 4);

    image->format.alpha_mask = 0x000000FF;
    image->format.red_mask = pic->format.i_rmask;
    image->format.blue_mask = pic->format.i_bmask;
    image->format.green_mask = pic->format.i_gmask;

    image->num_palette_entries = 0;
    image->entry_bytes = 0;

    if (pic->format.p_palette)
        CopyPalette(vd, image, pic->format.p_palette);

    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)", status);
        goto out_va_free;
    }

    return VLC_SUCCESS;

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

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

    subpicture_cache_t *cache = cache_new();
    if (!cache)
        return VLC_EGENERIC;
    cache->i_start = subpicture->i_start;
    cache->i_stop  = subpicture->i_stop;
#ifdef VAAPI_DEBUG
    msg_Dbg(vd, "link surface %d cache %d", surface->i_id, cache->i_id);
#endif
    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 */
        vasubpicture_cache_t *vasub_cache = subpicture_cache_new();
        if (!vasub_cache)
            goto cleanup;

        VAImage *image = &vasub_cache->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 id=%d", image->image_id);
            free(vasub_cache);
            goto cleanup;
        }

        /* Create Subpicture */
        vasub_cache->i_id = SubpictureCreate(vd, subpicture, region,
                                                 image, sys->sflags);
        if (vasub_cache->i_id == VA_INVALID_ID)
        {
            vaDestroyImage(sys->conn->p_display, image->image_id);
            vasub_cache->i_id = VA_INVALID_ID;
            image->image_id = VA_INVALID_ID;
            image->buf = VA_INVALID_ID;
            subpicture_cache_destroy(vasub_cache);
            vasub_cache = NULL;
            goto cleanup;
        }
#ifdef VAAPI_DEBUG
        msg_Dbg(vd, "+ %d: subid %d", i_sub, vasub_cache->i_id);
#endif
        err = SubpictureLink(vd, surface->i_id, subpicture, region,
                                 vasub_cache->i_id, sys->sflags);
        if (err != VLC_SUCCESS)
        {
            vaDestroySubpicture(sys->conn->p_display, vasub_cache->i_id);
            vaDestroyImage(sys->conn->p_display, image->image_id);
            vasub_cache->i_id = VA_INVALID_ID;
            image->image_id = VA_INVALID_ID;
            image->buf = VA_INVALID_ID;
            subpicture_cache_destroy(vasub_cache);
            vasub_cache = NULL;
            goto cleanup;
        }

        vlc_array_append(&cache->subpictures, (void *)vasub_cache);
        vasub_cache = NULL;

        i_sub++;
    }

    /* remember what was created, so it can be released in DisplayPicture() */
    vlc_mutex_lock(&sys->cache_lock);
    vlc_array_append(&sys->cache, (void *)cache);

    sys->render.i_cache = cache->i_id;
    surface->i_cache = cache->i_id;
    vlc_mutex_unlock(&sys->cache_lock);

    return VLC_SUCCESS;

cleanup:
    surface->i_cache = VA_INVALID_ID;

    msg_Err(vd, "unlink surface %d cache %d", surface->i_id, cache->i_id);

    for (int i = 0; i < i_sub; i++)
    {
        vasubpicture_cache_t *sub = vlc_array_item_at_index(&cache->subpictures, i);
        assert(sub);
        msg_Err(vd, "- %u: subid %d", i, sub->i_id);
        VAStatus status = SubpictureUnlink(sys->conn, surface->i_id, sub->i_id);
        if (status != VA_STATUS_SUCCESS)
            msg_Err(vd, "failed unlinking (error %d)", status);
    }

    SubpictureCacheClean(vd, sys->conn, cache);
    cache_destroy(cache);
    cache = NULL;
    return VLC_EGENERIC;
}

static int RenderCachedSubpictures(vout_display_t *vd, picture_t *picture,
                                   subpicture_t *subpicture, subpicture_cache_t *cache)
{
    vout_display_sys_t *sys = vd->sys;
    vlc_va_surface_t *surface = picture->p_sys->surface;

    const int count = vlc_array_count(&cache->subpictures);
#ifdef VAAPI_DEBUG
    msg_Dbg(vd, "link surface %d cache %d size %d",
                surface->i_id, cache->i_id, count);
#endif

    int i_sub = 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)
        {
            if (i_sub >= count)
                break;

            vasubpicture_cache_t *vasubpic;
            vasubpic = (vasubpicture_cache_t *) vlc_array_item_at_index(&cache->subpictures, i_sub);
            if (!vasubpic)
                goto cleanup;

            if ((vasubpic->image.width  != region->fmt.i_width) ||
                (vasubpic->image.height != region->fmt.i_height))
                goto cleanup;

#ifdef VAAPI_DEBUG
            msg_Dbg(vd, "+ %d: subid %d", i_sub, vasubpic->i_id);
#endif
            int err = SubpictureLink(vd, surface->i_id, subpicture, region,
                                         vasubpic->i_id, sys->sflags);
            if (err != VLC_SUCCESS)
                goto cleanup;

            vasubpic = NULL;
            i_sub++;
        }
    }

    /* FIXME: should not happen */
    if (i_sub < count)
        goto cleanup;
    surface->i_cache = cache->i_id;
    return VLC_SUCCESS;

cleanup:
    surface->i_cache = VA_INVALID_ID;
    msg_Warn(vd, "unlink surface %d cache %d", surface->i_id, cache->i_id);
    for (int i = 0; i < i_sub; i++)
    {
        vasubpicture_cache_t *vasubpic;
        vasubpic = (vasubpicture_cache_t *) vlc_array_item_at_index(&cache->subpictures, i);
        assert(vasubpic);
        msg_Warn(vd, "- %u: subid %d", i, vasubpic->i_id);
        VAStatus status = SubpictureUnlink(sys->conn, surface->i_id, vasubpic->i_id);
        if (status != VA_STATUS_SUCCESS)
            msg_Err(vd, "- %d unlink failed (status = %d)", i, status);
    }
    return VLC_EGENERIC;
}

static void PictureRelease(picture_t *picture)
{
    if (picture->i_refcount > 0)
        picture->i_refcount--;
    if (picture->p_sys)
    {
        vlc_va_surface_t *surface = picture->p_sys->surface;

        if (surface && (surface->i_cache != VA_INVALID_ID))
        {
#if 0 /* FIXME: leaking subpicture associations */
            vlc_va_conn_t *conn = vlc_va_get_conn();

            vlc_mutex_lock(&sys->cache_lock);
            subpicture_cache_t *cache = cache_find(&sys->cache, surface->i_cache);
            assert(cache);
            const int count = vlc_array_count(&cache->subpictures);
            for (int i = 0; i < count; i++)
            {
                vasubpicture_cache_t *vasubpic;
                vasubpic = (vasubpicture_cache_t *) vlc_array_item_at_index(&cache->subpictures, i_sub);
                assert(vasubpic);
                VAStatus status = SubpictureUnlink(conn, surface->i_id, vasubpic->i_id);
                if (status != VA_STATUS_SUCCESS)
                    msg_Err(vd, "- %d unlink failed (status = %d)", i, status);
            }
            vlc_mutex_unlock(&sys->cache_lock);
#endif
        }
        /* */
        picture->p_sys->surface = NULL;
    }
    free(picture->p_sys);
    picture->p_sys = NULL;
}

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

    if (!subpicture)
        return;

    assert(!subpicture->b_ephemer);

    /* Cleanup for reclaimed surface */
    if (picture->p_sys && picture->p_sys->surface &&
        (picture->p_sys->surface->i_cache != VA_INVALID_ID) &&
        (picture->format.i_chroma == VLC_CODEC_VAAPI_SURFACE))
    {
        assert(0);
#if 0
        vlc_va_surface_t *surface = picture->p_sys->surface;
        if (surface->i_cache != VA_INVALID_ID)
        {
            vlc_mutex_lock(&sys->cache_lock);
#ifdef VAAPI_DEBUG
            msg_Dbg(vd, "unlink surface %d cache %d", surface->i_id, surface->i_cache);
#endif
            subpicture_cache_t *cache = cache_find(&sys->cache, surface->i_cache);
            assert(cache);
            const int count = vlc_array_count(&cache->subpictures);
            for (unsigned int i = 0; i < count; i++)
            {
                vasubpicture_cache_t *sub = vlc_array_item_at_index(&cache->subpictures, i);
                assert(sub);
#ifdef VAAPI_DEBUG
                msg_Dbg(vd, "- %u: subid %d", i, sub->i_id);
#endif
                VAStatus status = SubpictureUnlink(sys->conn, surface->i_id, sub->i_id);
                if (status != VA_STATUS_SUCCESS)
                    msg_Err(vd, "failed unlinking (error %d)", status);
            }
            vlc_mutex_unlock(&sys->cache_lock);
        }

#endif
    }

    assert(picture->format.i_chroma == VLC_CODEC_VAAPI_SURFACE);

    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)
        return;
again:
    /* same as previous subpicture? */
    if ((sys->render.prev_subpicture == subpicture) &&
        (sys->render.i_cache != VA_INVALID_ID) &&
        (sys->render.i_start == subpicture->i_start) &&
        (sys->render.i_stop  == subpicture->i_stop))
    {
        assert(sys->render.i_cache != VA_INVALID_ID);

        vlc_mutex_lock(&sys->cache_lock);
#ifdef VAAPI_DEBUG
        msg_Dbg(vd, "reuse subpicture %p prev %p cache %d",
                 subpicture, sys->render.prev_subpicture,
                 sys->render.i_cache);
#endif
        subpicture_cache_t *cache = (subpicture_cache_t *) cache_find(&sys->cache, sys->render.i_cache);
        if (!cache)
        {
            msg_Dbg(vd, "did not find cached subpictures, resetting");
            sys->render.i_cache = VA_INVALID_ID;
            sys->render.prev_subpicture = NULL;
            vlc_mutex_unlock(&sys->cache_lock);
            goto again;
        }
        if ((cache->i_start != subpicture->i_start) &&
            (cache->i_stop  != subpicture->i_stop))
        {
            msg_Dbg(vd, "different cached subpictures, resetting");
            sys->render.i_cache = VA_INVALID_ID;
            sys->render.prev_subpicture = NULL;
            vlc_mutex_unlock(&sys->cache_lock);
            goto again;
        }
        if (RenderCachedSubpictures(vd, picture, subpicture, cache) != VLC_SUCCESS)
        {
            msg_Dbg(vd, "failed using cached subpictures, resetting");
            sys->render.i_cache = VA_INVALID_ID;
            sys->render.prev_subpicture = NULL;
            vlc_mutex_unlock(&sys->cache_lock);
            goto again;
        }
        vlc_mutex_unlock(&sys->cache_lock);
    }
    else
    {
#ifdef VAAPI_DEBUG
        msg_Dbg(vd, "new subpicture %p prev %p", subpicture, sys->render.prev_subpicture);
#endif
        if (RenderDirectSubpicture(vd, picture, subpicture) != VLC_SUCCESS)
        {
            msg_Err(vd, "failed rendering subtitles");
            return;
        }
        sys->render.prev_subpicture = subpicture;
        sys->render.i_start = subpicture->i_start;
        sys->render.i_stop  = subpicture->i_stop;
    }
}

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: %d (surface id=%d): %p",
                status, surface, picture);
}

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)
    {
        assert(pic->format.i_chroma == VLC_CODEC_VAAPI_SURFACE);
        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->format.i_chroma == VLC_CODEC_VAAPI_SURFACE);
        msg_Err(vd, "discarding picture without surface information: %p", pic);
        if (subpicture)
            subpicture_Delete(subpicture);
        picture_Release(pic);
        return;
    }
    assert(pic->format.i_chroma == VLC_CODEC_VAAPI_SURFACE);

    vlc_va_surface_t *surface = pic->p_sys->surface;

    unsigned int i_order = (sys->i_display_order > 0) ?
        sys->i_display_order++ : surface->i_order;
    if ((i_order != 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)",
                surface->i_id, surface->i_order, surface->i_refcount, pic);
        if (surface->i_refcount > 1)
            surface->i_refcount = 1;

        goto cleanup;
    }

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

cleanup:
    if (surface->i_cache != VA_INVALID_ID)
    {
        vlc_mutex_lock(&sys->cache_lock);
#ifdef VAAPI_DEBUG
        msg_Dbg(vd, "unlink surface %d cache %d", surface->i_id, surface->i_cache);
#endif
        subpicture_cache_t *cache = cache_find(&sys->cache, surface->i_cache);
        assert(cache);
        const int count = vlc_array_count(&cache->subpictures);
        for (int i = 0; i < count; i++)
        {
            vasubpicture_cache_t *sub = vlc_array_item_at_index(&cache->subpictures, i);
            assert(sub);
#ifdef VAAPI_DEBUG
            msg_Dbg(vd, "- %u: subid %d", i, sub->i_id);
#endif
            VAStatus status = SubpictureUnlink(sys->conn, surface->i_id, sub->i_id);
            if (status != VA_STATUS_SUCCESS)
                msg_Err(vd, "failed unlinking (error %d)", status);
        }
        vlc_mutex_unlock(&sys->cache_lock);
    }

    if (subpicture &&
        (mdate() > subpicture->i_stop) &&
        (surface->i_cache != VA_INVALID_ID) &&
        (surface->i_cache != sys->display.i_cache))
    {
        if (sys->display.i_cache != VA_INVALID_ID)
        {
            vlc_mutex_lock(&sys->cache_lock);

            assert(sys->display.i_cache != surface->i_cache);
#ifdef VAAPI_DEBUG
            msg_Dbg(vd, "display subpicture %p cache size %d, search for cache %d",
                    subpicture, vlc_array_count(&sys->cache), sys->display.i_cache);
#endif
            subpicture_cache_t *cache;
            cache = (subpicture_cache_t*) cache_find(&sys->cache, sys->display.i_cache);
            assert(cache);
            int index = vlc_array_index_of_item(&sys->cache, (void*)cache);
            vlc_array_remove(&sys->cache, index);
            SubpictureCacheClean(vd, sys->conn, cache);
            cache_destroy(cache);
            cache = NULL;
            vlc_mutex_unlock(&sys->cache_lock);
        }
    }

    sys->display.i_cache = surface->i_cache;
    surface->i_cache = VA_INVALID_ID;

    ReleaseSurface(surface);

    if (subpicture)
        subpicture_Delete(subpicture);

    pic->p_sys->surface = NULL;
    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->surface = NULL;

        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 */
