/*
 *  Copyright (C) 2012 Intel Corporation.  All Rights Reserved.
 *
 *  This 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 software 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 software; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 *  USA.
 */

#ifdef LIBVNCSERVER_CONFIG_LIBVA

#include <X11/Xlib.h>
#include <va/va_version.h>
#if VA_CHECK_VERSION(0,34,0)
#include <va/va_compat.h>
#endif
#include <va/va_x11.h>

enum _slice_types {
	SLICE_TYPE_P = 0,  /* Predicted */
	SLICE_TYPE_B = 1,  /* Bi-predicted */
	SLICE_TYPE_I = 2,  /* Intra coded */
};

#define SURFACE_NUM     7

VADisplay       va_dpy = NULL;
VAConfigID      va_config_id;
VASurfaceID     va_surface_id[SURFACE_NUM];
VAContextID     va_context_id = 0;

VABufferID      va_pic_param_buf_id[SURFACE_NUM];
VABufferID      va_mat_param_buf_id[SURFACE_NUM];
VABufferID      va_sp_param_buf_id[SURFACE_NUM];
VABufferID      va_d_param_buf_id[SURFACE_NUM];

static int cur_height = 0;
static int cur_width = 0;
static unsigned int num_frames = 0;
static int sid = 0;
static unsigned int frame_id = 0;
static int field_order_count = 0;
static VASurfaceID curr_surface = VA_INVALID_ID;

VAStatus gva_status;
VASurfaceStatus gsurface_status;
#define CHECK_SURF(X) \
    gva_status = vaQuerySurfaceStatus(va_dpy, X, &gsurface_status); \
    if (gsurface_status != 4) printf("ss: %d\n", gsurface_status);

#ifdef _DEBUG
#define DebugLog(A) rfbClientLog A
#else
#define DebugLog(A)
#endif

#define CHECK_VASTATUS(va_status,func)                  \
    if (va_status != VA_STATUS_SUCCESS) {                   \
        /*fprintf(stderr,"%s:%s (%d) failed,exit\n", __func__, func, __LINE__);*/ \
        rfbClientErr("%s:%s:%d failed (0x%x),exit\n", __func__, func, __LINE__, va_status); \
        exit(1);                                \
    } else  { \
        /*fprintf(stderr,">> SUCCESS for: %s:%s (%d)\n", __func__, func, __LINE__);*/ \
        DebugLog(("%s:%s:%d success\n", __func__, func, __LINE__)); \
    }

/*
 * Forward declarations
 */
static void h264_decode_frame(int f_width, int f_height, char *framedata, int framesize, int slice_type);
static void SetVAPictureParameterBufferH264(VAPictureParameterBufferH264 *p, int width, int height);
static void SetVASliceParameterBufferH264(VASliceParameterBufferH264 *p);
static void SetVASliceParameterBufferH264_Intra(VASliceParameterBufferH264 *p, int first);

static void put_updated_rectangle(rfbClient *client, int x, int y, int width, int height, int f_width, int f_height, int first_for_frame);
static void nv12_to_rgba(const VAImage vaImage, rfbClient *client, int ch_x, int ch_y, int ch_w, int ch_h);


/* FIXME: get this value from the server instead of hardcoding 32bit pixels */
#define BPP (4 * 8)

static const char *string_of_FOURCC(uint32_t fourcc)
{
    static int buf;
    static char str[2][5];

    buf ^= 1;
    str[buf][0] = fourcc;
    str[buf][1] = fourcc >> 8;
    str[buf][2] = fourcc >> 16;
    str[buf][3] = fourcc >> 24;
    str[buf][4] = '\0';
    return str[buf];
}

static inline const char *string_of_VAImageFormat(VAImageFormat *imgfmt)
{
    return string_of_FOURCC(imgfmt->fourcc);
}


static rfbBool
HandleH264 (rfbClient* client, int rx, int ry, int rw, int rh)
{
    rfbH264Header hdr;
    char *framedata;

    DebugLog(("Framebuffer update with H264 (x: %d, y: %d, w: %d, h: %d)\n", rx, ry, rw, rh));

    /* First, read the frame size and allocate buffer to store the data */
    if (!ReadFromRFBServer(client, (char *)&hdr, sz_rfbH264Header))
        return FALSE;

    hdr.slice_type = rfbClientSwap32IfLE(hdr.slice_type);
    hdr.nBytes = rfbClientSwap32IfLE(hdr.nBytes);
    hdr.width = rfbClientSwap32IfLE(hdr.width);
    hdr.height = rfbClientSwap32IfLE(hdr.height);

    framedata = (char*) malloc(hdr.nBytes);

    /* Obtain frame data from the server */
    DebugLog(("Reading %d bytes of frame data (type: %d)\n", hdr.nBytes, hdr.slice_type));
    if (!ReadFromRFBServer(client, framedata, hdr.nBytes))
        return FALSE;

    /* First make sure we have a large enough raw buffer to hold the
     * decompressed data.  In practice, with a fixed BPP, fixed frame
     * buffer size and the first update containing the entire frame
     * buffer, this buffer allocation should only happen once, on the
     * first update.
     */
    if ( client->raw_buffer_size < (( rw * rh ) * ( BPP / 8 ))) {
        if ( client->raw_buffer != NULL ) {
            free( client->raw_buffer );
        }

        client->raw_buffer_size = (( rw * rh ) * ( BPP / 8 ));
        client->raw_buffer = (char*) malloc( client->raw_buffer_size );
        rfbClientLog("Allocated raw buffer of %d bytes (%dx%dx%d BPP)\n", client->raw_buffer_size, rw, rh, BPP);
    }

    /* Decode frame if frame data was sent. Server only sends frame data for the first
     * framebuffer update message for a particular frame buffer contents.
     * If more than 1 rectangle is updated, the messages after the first one (with
     * the H.264 frame) have nBytes == 0.
     */
    if (hdr.nBytes > 0) {
        DebugLog(("  decoding %d bytes of H.264 data\n", hdr.nBytes));
        h264_decode_frame(hdr.width, hdr.height, framedata, hdr.nBytes, hdr.slice_type);
    }

    DebugLog(("  updating rectangle (%d, %d)-(%d, %d)\n", rx, ry, rw, rh));
    put_updated_rectangle(client, rx, ry, rw, rh, hdr.width, hdr.height, hdr.nBytes != 0);

    free(framedata);

    return TRUE;
}

static void h264_cleanup_decoder()
{
    VAStatus va_status;

    rfbClientLog("%s()\n", __FUNCTION__);

    if (va_surface_id[0] != VA_INVALID_ID) {
        va_status = vaDestroySurfaces(va_dpy, &va_surface_id[0], SURFACE_NUM);
        CHECK_VASTATUS(va_status, "vaDestroySurfaces");
    }

    if (va_context_id) {
        va_status = vaDestroyContext(va_dpy, va_context_id);
        CHECK_VASTATUS(va_status, "vaDestroyContext");
        va_context_id = 0;
    }

    num_frames = 0;
    sid = 0;
    frame_id = 0;
    field_order_count = 0;
}

static void h264_init_decoder(int width, int height)
{
    VAStatus va_status;

    if (va_context_id) {
        rfbClientLog("%s: va_dpy already initialized\n", __FUNCTION__);
    }

    if (va_dpy != NULL) {
        rfbClientLog("%s: Re-initializing H.264 decoder\n", __FUNCTION__);
    }
    else {
        rfbClientLog("%s: initializing H.264 decoder\n", __FUNCTION__);

        /* Attach VA display to local X display */
        Display *win_display = (Display *)XOpenDisplay(":0.0");
        if (win_display == NULL) {
            rfbClientErr("Can't connect to local display\n");
            exit(-1);
        }

        int major_ver, minor_ver;
        va_dpy = vaGetDisplay(win_display);
        va_status = vaInitialize(va_dpy, &major_ver, &minor_ver);
        CHECK_VASTATUS(va_status, "vaInitialize");
        rfbClientLog("%s: libva version %d.%d found\n", __FUNCTION__, major_ver, minor_ver);
    }

    /* Check for VLD entrypoint */
    int num_entrypoints;
    VAEntrypoint    entrypoints[5];
    int vld_entrypoint_found = 0;

    /* Change VAProfileH264High if needed */
    VAProfile profile = VAProfileH264High;
    va_status = vaQueryConfigEntrypoints(va_dpy, profile, entrypoints, &num_entrypoints);
    CHECK_VASTATUS(va_status, "vaQueryConfigEntrypoints");
    int i;
    for (i = 0; i < num_entrypoints; ++i) {
        if (entrypoints[i] == VAEntrypointVLD) {
            vld_entrypoint_found = 1;
            break;
        }
    }

    if (vld_entrypoint_found == 0) {
        rfbClientErr("VLD entrypoint not found\n");
        exit(1);
    }

    /* Create configuration for the decode pipeline */
    VAConfigAttrib attrib;
    attrib.type = VAConfigAttribRTFormat;
    va_status = vaCreateConfig(va_dpy, profile, VAEntrypointVLD, &attrib, 1, &va_config_id);
    CHECK_VASTATUS(va_status, "vaCreateConfig");

    /* Create VA surfaces */
    for (i = 0; i < SURFACE_NUM; ++i) {
        va_surface_id[i]       = VA_INVALID_ID;
        va_pic_param_buf_id[i] = VA_INVALID_ID;
        va_mat_param_buf_id[i] = VA_INVALID_ID;
        va_sp_param_buf_id[i]  = VA_INVALID_ID;
        va_d_param_buf_id[i]   = VA_INVALID_ID;
    }
    va_status = vaCreateSurfaces(va_dpy, width, height, VA_RT_FORMAT_YUV420, SURFACE_NUM, &va_surface_id[0]);
    CHECK_VASTATUS(va_status, "vaCreateSurfaces");
    for (i = 0; i < SURFACE_NUM; ++i) {
        DebugLog(("%s: va_surface_id[%d] = %p\n", __FUNCTION__, i, va_surface_id[i]));
    }

    /* Create VA context */
    va_status = vaCreateContext(va_dpy, va_config_id, width, height, 0/*VA_PROGRESSIVE*/,  &va_surface_id[0], SURFACE_NUM, &va_context_id);
    CHECK_VASTATUS(va_status, "vaCreateContext");
    DebugLog(("%s: VA context created (id: %d)\n", __FUNCTION__, va_context_id));


    /* Instantiate decode pipeline */
    va_status = vaBeginPicture(va_dpy, va_context_id, va_surface_id[0]);
    CHECK_VASTATUS(va_status, "vaBeginPicture");

    rfbClientLog("%s: H.264 decoder initialized\n", __FUNCTION__);
}

static void h264_decode_frame(int f_width, int f_height, char *framedata, int framesize, int slice_type)
{
    VAStatus va_status;

    DebugLog(("%s: called for frame of %d bytes (%dx%d) slice_type=%d\n", __FUNCTION__, framesize, width, height, slice_type));

    /* Initialize decode pipeline if necessary */
    if ( (f_width > cur_width) || (f_height > cur_height) ) {
        if (va_dpy != NULL)
            h264_cleanup_decoder();
        cur_width = f_width;
        cur_height = f_height;

        h264_init_decoder(f_width, f_height);
        rfbClientLog("%s: decoder initialized\n", __FUNCTION__);
    }

    /* Decode frame */
    static VAPictureH264 va_picture_h264, va_old_picture_h264;

    /* The server should always send an I-frame when a new client connects
     * or when the resolution of the framebuffer changes, but we check
     * just in case.
     */
    if ( (slice_type != SLICE_TYPE_I) && (num_frames == 0) ) {
        rfbClientLog("First frame is not an I frame !!! Skipping!!!\n");
        return;
    }

    DebugLog(("%s: frame_id=%d va_surface_id[%d]=0x%x field_order_count=%d\n", __FUNCTION__, frame_id, sid, va_surface_id[sid], field_order_count));

    va_picture_h264.picture_id = va_surface_id[sid];
    va_picture_h264.frame_idx  = frame_id;
    va_picture_h264.flags = 0;
    va_picture_h264.BottomFieldOrderCnt = field_order_count;
    va_picture_h264.TopFieldOrderCnt = field_order_count;

    /* Set up picture parameter buffer */
    if (va_pic_param_buf_id[sid] == VA_INVALID_ID) {
        va_status = vaCreateBuffer(va_dpy, va_context_id, VAPictureParameterBufferType, sizeof(VAPictureParameterBufferH264), 1, NULL, &va_pic_param_buf_id[sid]);
        CHECK_VASTATUS(va_status, "vaCreateBuffer(PicParam)");
    }
    CHECK_SURF(va_surface_id[sid]);

    VAPictureParameterBufferH264 *pic_param_buf = NULL;
    va_status = vaMapBuffer(va_dpy, va_pic_param_buf_id[sid], (void **)&pic_param_buf);
    CHECK_VASTATUS(va_status, "vaMapBuffer(PicParam)");

    SetVAPictureParameterBufferH264(pic_param_buf, f_width, f_height);
    memcpy(&pic_param_buf->CurrPic, &va_picture_h264, sizeof(VAPictureH264));

    if (slice_type == SLICE_TYPE_P) {
        memcpy(&pic_param_buf->ReferenceFrames[0], &va_old_picture_h264, sizeof(VAPictureH264));
        pic_param_buf->ReferenceFrames[0].flags = 0;
    }
    else if (slice_type != SLICE_TYPE_I) {
        rfbClientLog("Frame type %d not supported!!!\n");
        return;
    }
    pic_param_buf->frame_num = frame_id;

    va_status = vaUnmapBuffer(va_dpy, va_pic_param_buf_id[sid]);
    CHECK_VASTATUS(va_status, "vaUnmapBuffer(PicParam)");

    /* Set up IQ matrix buffer */
    if (va_mat_param_buf_id[sid] == VA_INVALID_ID) {
        va_status = vaCreateBuffer(va_dpy, va_context_id, VAIQMatrixBufferType, sizeof(VAIQMatrixBufferH264), 1, NULL, &va_mat_param_buf_id[sid]);
        CHECK_VASTATUS(va_status, "vaCreateBuffer(IQMatrix)");
    }
    CHECK_SURF(va_surface_id[sid]);

    VAIQMatrixBufferH264 *iq_matrix_buf = NULL;
    va_status = vaMapBuffer(va_dpy, va_mat_param_buf_id[sid], (void **)&iq_matrix_buf);
    CHECK_VASTATUS(va_status, "vaMapBuffer(IQMatrix)");

    static const unsigned char m_MatrixBufferH264[]= {
        /* ScalingList4x4[6][16] */
        0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
        0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
        0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
        0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
        0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
        0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
        /* ScalingList8x8[2][64] */
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
    };

    memcpy(iq_matrix_buf, m_MatrixBufferH264, 224);
    va_status = vaUnmapBuffer(va_dpy, va_mat_param_buf_id[sid]);
    CHECK_VASTATUS(va_status, "vaUnmapBuffer(IQMatrix)");

    VABufferID buffer_ids[2];
    buffer_ids[0] = va_pic_param_buf_id[sid];
    buffer_ids[1] = va_mat_param_buf_id[sid];

    CHECK_SURF(va_surface_id[sid]);
    va_status = vaRenderPicture(va_dpy, va_context_id, buffer_ids, 2);
    CHECK_VASTATUS(va_status, "vaRenderPicture");

    /* Set up slice parameter buffer */
    if (va_sp_param_buf_id[sid] == VA_INVALID_ID) {
        va_status = vaCreateBuffer(va_dpy, va_context_id, VASliceParameterBufferType, sizeof(VASliceParameterBufferH264), 1, NULL, &va_sp_param_buf_id[sid]);
        CHECK_VASTATUS(va_status, "vaCreateBuffer(SliceParam)");
    }
    CHECK_SURF(va_surface_id[sid]);

    VASliceParameterBufferH264 *slice_param_buf = NULL;
    va_status = vaMapBuffer(va_dpy, va_sp_param_buf_id[sid], (void **)&slice_param_buf);
    CHECK_VASTATUS(va_status, "vaMapBuffer(SliceParam)");

    static int t2_first = 1;
    if (slice_type == SLICE_TYPE_I) {
        SetVASliceParameterBufferH264_Intra(slice_param_buf, t2_first);
        t2_first = 0;
    } else {
        SetVASliceParameterBufferH264(slice_param_buf);
        memcpy(&slice_param_buf->RefPicList0[0], &va_old_picture_h264, sizeof(VAPictureH264));
        slice_param_buf->RefPicList0[0].flags = 0;
    }
    slice_param_buf->slice_data_bit_offset = 0;
    slice_param_buf->slice_data_size = framesize;

    va_status = vaUnmapBuffer(va_dpy, va_sp_param_buf_id[sid]);
    CHECK_VASTATUS(va_status, "vaUnmapBuffer(SliceParam)");
    CHECK_SURF(va_surface_id[sid]);

    /* Set up slice data buffer and copy H.264 encoded data */
    if (va_d_param_buf_id[sid] == VA_INVALID_ID) {
        /* TODO use estimation matching framebuffer dimensions instead of this large value */
        va_status = vaCreateBuffer(va_dpy, va_context_id, VASliceDataBufferType, 4177920, 1, NULL, &va_d_param_buf_id[sid]); /* 1080p size */
        CHECK_VASTATUS(va_status, "vaCreateBuffer(SliceData)");
    }

    char *slice_data_buf;
    va_status = vaMapBuffer(va_dpy, va_d_param_buf_id[sid], (void **)&slice_data_buf);
    CHECK_VASTATUS(va_status, "vaMapBuffer(SliceData)");
    memcpy(slice_data_buf, framedata, framesize);

    CHECK_SURF(va_surface_id[sid]);
    va_status = vaUnmapBuffer(va_dpy, va_d_param_buf_id[sid]);
    CHECK_VASTATUS(va_status, "vaUnmapBuffer(SliceData)");

    buffer_ids[0] = va_sp_param_buf_id[sid];
    buffer_ids[1] = va_d_param_buf_id[sid];

    CHECK_SURF(va_surface_id[sid]);
    va_status = vaRenderPicture(va_dpy, va_context_id, buffer_ids, 2);
    CHECK_VASTATUS(va_status, "vaRenderPicture");

    va_status = vaEndPicture(va_dpy, va_context_id);
    CHECK_VASTATUS(va_status, "vaEndPicture");

    /* Prepare next one... */
    int sid_new = (sid + 1) % SURFACE_NUM;
    DebugLog(("%s: new Surface ID = %d\n", __FUNCTION__, sid_new));
    va_status = vaBeginPicture(va_dpy, va_context_id, va_surface_id[sid_new]);
    CHECK_VASTATUS(va_status, "vaBeginPicture");

    /* Get decoded data */
    va_status = vaSyncSurface(va_dpy, va_surface_id[sid]);
    CHECK_VASTATUS(va_status, "vaSyncSurface");
    CHECK_SURF(va_surface_id[sid]);

    curr_surface = va_surface_id[sid];

    sid = sid_new;

    field_order_count += 2;
    ++frame_id;
    if (frame_id > 15) {
        frame_id = 0;
    }

    ++num_frames;

    memcpy(&va_old_picture_h264, &va_picture_h264, sizeof(VAPictureH264));
}

static void put_updated_rectangle(rfbClient *client, int x, int y, int width, int height, int f_width, int f_height, int first_for_frame)
{
    if (curr_surface == VA_INVALID_ID) {
        rfbClientErr("%s: called, but current surface is invalid\n", __FUNCTION__);
        return;
    }

    VAStatus va_status;

    if (client->outputWindow) {
        /* use efficient vaPutSurface() method of putting the framebuffer on the screen */
        if (first_for_frame) {
            /* vaPutSurface() clears window contents outside the given destination rectangle => always update full screen. */
            va_status = vaPutSurface(va_dpy, curr_surface, client->outputWindow, 0, 0, f_width, f_height, 0, 0, f_width, f_height, NULL, 0, VA_FRAME_PICTURE);
            CHECK_VASTATUS(va_status, "vaPutSurface");
        }
    }
    else if (client->frameBuffer) {
        /* ... or copy the changed framebuffer region manually as a fallback */
        VAImage decoded_image;
        decoded_image.image_id = VA_INVALID_ID;
        decoded_image.buf      = VA_INVALID_ID;
        va_status = vaDeriveImage(va_dpy, curr_surface, &decoded_image);
        CHECK_VASTATUS(va_status, "vaDeriveImage");

        if ((decoded_image.image_id == VA_INVALID_ID) || (decoded_image.buf == VA_INVALID_ID)) {
            rfbClientErr("%s: vaDeriveImage() returned success but VA image is invalid (id: %d, buf: %d)\n", __FUNCTION__, decoded_image.image_id, decoded_image.buf);
        }

        nv12_to_rgba(decoded_image, client, x, y, width, height);

        va_status = vaDestroyImage(va_dpy, decoded_image.image_id);
        CHECK_VASTATUS(va_status, "vaDestroyImage");
    }
}

static void SetVAPictureParameterBufferH264(VAPictureParameterBufferH264 *p, int width, int height)
{
    int i;
    unsigned int width_in_mbs = (width + 15) / 16;
    unsigned int height_in_mbs = (height + 15) / 16;

    memset(p, 0, sizeof(VAPictureParameterBufferH264));
    p->picture_width_in_mbs_minus1 = width_in_mbs - 1;
    p->picture_height_in_mbs_minus1 = height_in_mbs - 1;
    p->num_ref_frames = 1;
    p->seq_fields.value = 145;
    p->pic_fields.value = 0x501;
    for (i = 0; i < 16; i++) {
        p->ReferenceFrames[i].flags = VA_PICTURE_H264_INVALID;
        p->ReferenceFrames[i].picture_id = 0xffffffff;
    }
}

static void SetVASliceParameterBufferH264(VASliceParameterBufferH264 *p)
{
    int i;
    memset(p, 0, sizeof(VASliceParameterBufferH264));
    p->slice_data_size = 0;
    p->slice_data_bit_offset = 64;
    p->slice_alpha_c0_offset_div2 = 2;
    p->slice_beta_offset_div2 = 2;
    p->chroma_weight_l0_flag = 1;
    p->chroma_weight_l0[0][0]=1;
    p->chroma_offset_l0[0][0]=0;
    p->chroma_weight_l0[0][1]=1;
    p->chroma_offset_l0[0][1]=0;
    p->luma_weight_l1_flag = 1;
    p->chroma_weight_l1_flag = 1;
    p->luma_weight_l0[0]=0x01;
    for (i = 0; i < 32; i++) {
        p->RefPicList0[i].flags = VA_PICTURE_H264_INVALID;
        p->RefPicList1[i].flags = VA_PICTURE_H264_INVALID;
    }
    p->RefPicList1[0].picture_id = 0xffffffff;
}

static void SetVASliceParameterBufferH264_Intra(VASliceParameterBufferH264 *p, int first)
{
    int i;
    memset(p, 0, sizeof(VASliceParameterBufferH264));
    p->slice_data_size = 0;
    p->slice_data_bit_offset = 64;
    p->slice_alpha_c0_offset_div2 = 2;
    p->slice_beta_offset_div2 = 2;
    p->slice_type = 2;
    if (first) {
        p->luma_weight_l0_flag = 1;
        p->chroma_weight_l0_flag = 1;
        p->luma_weight_l1_flag = 1;
        p->chroma_weight_l1_flag = 1;
    } else {
        p->chroma_weight_l0_flag = 1;
        p->chroma_weight_l0[0][0]=1;
        p->chroma_offset_l0[0][0]=0;
        p->chroma_weight_l0[0][1]=1;
        p->chroma_offset_l0[0][1]=0;
        p->luma_weight_l1_flag = 1;
        p->chroma_weight_l1_flag = 1;
        p->luma_weight_l0[0]=0x01;
    }
    for (i = 0; i < 32; i++) {
        p->RefPicList0[i].flags = VA_PICTURE_H264_INVALID;
        p->RefPicList1[i].flags = VA_PICTURE_H264_INVALID;
    }
    p->RefPicList1[0].picture_id = 0xffffffff;
    p->RefPicList0[0].picture_id = 0xffffffff;
}

static void nv12_to_rgba(const VAImage vaImage, rfbClient *client, int ch_x, int ch_y, int ch_w, int ch_h)
{
    DebugLog(("%s: converting region (%d, %d)-(%d, %d) from NV12->RGBA\n", __FUNCTION__, ch_x, ch_y, ch_w, ch_h));

    VAStatus va_status;
    uint8_t *nv12_buf;
    va_status = vaMapBuffer(va_dpy, vaImage.buf, (void **)&nv12_buf);
    CHECK_VASTATUS(va_status, "vaMapBuffer(DecodedData)");

    /* adjust x, y, width, height of the affected area so
     * x, y, width and height are always even.
     */
    if (ch_x % 2) { --ch_x; ++ch_w; }
    if (ch_y % 2) { --ch_y; ++ch_h; }
    if ((ch_x + ch_w) % 2) { ++ch_w; }
    if ((ch_y + ch_h) % 2) { ++ch_h; }

    /* point nv12_buf and dst to upper left corner of changed area */
    uint8_t *nv12_y  = &nv12_buf[vaImage.offsets[0] + vaImage.pitches[0] * ch_y + ch_x];
    uint8_t *nv12_uv = &nv12_buf[vaImage.offsets[1] + vaImage.pitches[1] * (ch_y / 2) + ch_x];
    uint32_t *dst    = &((uint32_t*)client->frameBuffer)[client->width * ch_y + ch_x];

    /* TODO: optimize R, G, B calculation. Possible ways to do this:
     *       - use lookup tables
     *       - convert from floating point to integer arithmetic
     *       - use MMX/SSE to vectorize calculations
     *       - use GPU (VA VPP, shader...)
     */
    int src_x, src_y;
    for (src_y = 0; src_y < ch_h; src_y += 2) {
        for (src_x = 0; src_x < ch_w; src_x += 2) {
            uint8_t nv_u = nv12_uv[src_x];
            uint8_t nv_v = nv12_uv[src_x + 1];
            uint8_t nv_y[4] = { nv12_y[                     src_x], nv12_y[                     src_x + 1],
                                nv12_y[vaImage.pitches[0] + src_x], nv12_y[vaImage.pitches[0] + src_x + 1] };

        int i;
            for (i = 0; i < 4; ++i) {
                double R = 1.164 * (nv_y[i] - 16)                        + 1.596 * (nv_v - 128);
                double G = 1.164 * (nv_y[i] - 16) - 0.391 * (nv_u - 128) - 0.813 * (nv_v - 128);
                double B = 1.164 * (nv_y[i] - 16) + 2.018 * (nv_u - 128);

                /* clamp R, G, B values. For some Y, U, V combinations,
                 * the results of the above calculations fall outside of
                 * the range 0-255.
                 */
                if (R < 0.0) R = 0.0;
                if (G < 0.0) G = 0.0;
                if (B < 0.0) B = 0.0;
                if (R > 255.0) R = 255.0;
                if (G > 255.0) G = 255.0;
                if (B > 255.0) B = 255.0;

                dst[client->width * (i / 2) + src_x + (i % 2)] = 0
                               | ((unsigned int)(R + 0.5) << client->format.redShift)
                               | ((unsigned int)(G + 0.5) << client->format.greenShift)
                               | ((unsigned int)(B + 0.5) << client->format.blueShift);
            }
        }

        nv12_y  += 2 * vaImage.pitches[0];
        nv12_uv += vaImage.pitches[1];
        dst     += 2 * client->width;
    }

    CHECK_SURF(va_surface_id[sid]);
    va_status = vaUnmapBuffer(va_dpy, vaImage.buf);
    CHECK_VASTATUS(va_status, "vaUnmapBuffer(DecodedData)");
}

#endif /* LIBVNCSERVER_CONFIG_LIBVA */
