/*
 * Copyright © 2001 Keith Packard
 * Copyright © 2008 Intel Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * Authors:
 *    Eric Anholt <eric@anholt.net>
 *    Zhigang Gong <zhigang.gong@linux.intel.com>
 *
 */

#include <stdlib.h>

#include "glamor_priv.h"
/**
 * Sets the offsets to add to coordinates to make them address the same bits in
 * the backing drawable. These coordinates are nonzero only for redirected
 * windows.
 */
void
glamor_get_drawable_deltas(DrawablePtr drawable, PixmapPtr pixmap,
                           int *x, int *y)
{
#ifdef COMPOSITE
    if (drawable->type == DRAWABLE_WINDOW) {
        *x = -pixmap->screen_x;
        *y = -pixmap->screen_y;
        return;
    }
#endif

    *x = 0;
    *y = 0;
}

void
glamor_pixmap_init(ScreenPtr screen)
{

}

void
glamor_pixmap_fini(ScreenPtr screen)
{
}

void
glamor_set_destination_pixmap_fbo(glamor_pixmap_fbo *fbo, int x0, int y0,
                                  int width, int height)
{
    glamor_make_current(fbo->glamor_priv);

    glBindFramebuffer(GL_FRAMEBUFFER, fbo->fb);
    glViewport(x0, y0, width, height);
}

void
glamor_set_destination_pixmap_priv_nc(glamor_pixmap_private *pixmap_priv)
{
    int w, h;

    PIXMAP_PRIV_GET_ACTUAL_SIZE(pixmap_priv, w, h);
    glamor_set_destination_pixmap_fbo(pixmap_priv->base.fbo, 0, 0, w, h);
}

int
glamor_set_destination_pixmap_priv(glamor_pixmap_private *pixmap_priv)
{
    if (!GLAMOR_PIXMAP_PRIV_HAS_FBO(pixmap_priv))
        return -1;

    glamor_set_destination_pixmap_priv_nc(pixmap_priv);
    return 0;
}

int
glamor_set_destination_pixmap(PixmapPtr pixmap)
{
    int err;
    glamor_pixmap_private *pixmap_priv = glamor_get_pixmap_private(pixmap);

    err = glamor_set_destination_pixmap_priv(pixmap_priv);
    return err;
}

Bool
glamor_set_planemask(PixmapPtr pixmap, unsigned long planemask)
{
    if (glamor_pm_is_solid(&pixmap->drawable, planemask)) {
        return GL_TRUE;
    }

    glamor_fallback("unsupported planemask %lx\n", planemask);
    return GL_FALSE;
}

Bool
glamor_set_alu(ScreenPtr screen, unsigned char alu)
{
    glamor_screen_private *glamor_priv = glamor_get_screen_private(screen);

    if (glamor_priv->gl_flavor == GLAMOR_GL_ES2) {
        if (alu != GXcopy)
            return FALSE;
        else
            return TRUE;
    }

    if (alu == GXcopy) {
        glDisable(GL_COLOR_LOGIC_OP);
        return TRUE;
    }
    glEnable(GL_COLOR_LOGIC_OP);
    switch (alu) {
    case GXclear:
        glLogicOp(GL_CLEAR);
        break;
    case GXand:
        glLogicOp(GL_AND);
        break;
    case GXandReverse:
        glLogicOp(GL_AND_REVERSE);
        break;
    case GXandInverted:
        glLogicOp(GL_AND_INVERTED);
        break;
    case GXnoop:
        glLogicOp(GL_NOOP);
        break;
    case GXxor:
        glLogicOp(GL_XOR);
        break;
    case GXor:
        glLogicOp(GL_OR);
        break;
    case GXnor:
        glLogicOp(GL_NOR);
        break;
    case GXequiv:
        glLogicOp(GL_EQUIV);
        break;
    case GXinvert:
        glLogicOp(GL_INVERT);
        break;
    case GXorReverse:
        glLogicOp(GL_OR_REVERSE);
        break;
    case GXcopyInverted:
        glLogicOp(GL_COPY_INVERTED);
        break;
    case GXorInverted:
        glLogicOp(GL_OR_INVERTED);
        break;
    case GXnand:
        glLogicOp(GL_NAND);
        break;
    case GXset:
        glLogicOp(GL_SET);
        break;
    default:
        glamor_fallback("unsupported alu %x\n", alu);
        return FALSE;
    }

    return TRUE;
}

/*
 * Map picture's format to the correct gl texture format and type.
 * no_alpha is used to indicate whehter we need to wire alpha to 1.
 *
 * Although opengl support A1/GL_BITMAP, we still don't use it
 * here, it seems that mesa has bugs when uploading a A1 bitmap.
 *
 * Return 0 if find a matched texture type. Otherwise return -1.
 **/
static int
glamor_get_tex_format_type_from_pictformat_gl(PictFormatShort format,
                                              GLenum *tex_format,
                                              GLenum *tex_type,
                                              int *no_alpha,
                                              int *revert,
                                              int *swap_rb, int is_upload)
{
    *no_alpha = 0;
    *revert = REVERT_NONE;
    *swap_rb = is_upload ? SWAP_NONE_UPLOADING : SWAP_NONE_DOWNLOADING;
    switch (format) {
    case PICT_a1:
        *tex_format = GL_ALPHA;
        *tex_type = GL_UNSIGNED_BYTE;
        *revert = is_upload ? REVERT_UPLOADING_A1 : REVERT_DOWNLOADING_A1;
        break;
    case PICT_b8g8r8x8:
        *no_alpha = 1;
    case PICT_b8g8r8a8:
        *tex_format = GL_BGRA;
        *tex_type = GL_UNSIGNED_INT_8_8_8_8;
        break;

    case PICT_x8r8g8b8:
        *no_alpha = 1;
    case PICT_a8r8g8b8:
        *tex_format = GL_BGRA;
        *tex_type = GL_UNSIGNED_INT_8_8_8_8_REV;
        break;
    case PICT_x8b8g8r8:
        *no_alpha = 1;
    case PICT_a8b8g8r8:
        *tex_format = GL_RGBA;
        *tex_type = GL_UNSIGNED_INT_8_8_8_8_REV;
        break;
    case PICT_x2r10g10b10:
        *no_alpha = 1;
    case PICT_a2r10g10b10:
        *tex_format = GL_BGRA;
        *tex_type = GL_UNSIGNED_INT_2_10_10_10_REV;
        break;
    case PICT_x2b10g10r10:
        *no_alpha = 1;
    case PICT_a2b10g10r10:
        *tex_format = GL_RGBA;
        *tex_type = GL_UNSIGNED_INT_2_10_10_10_REV;
        break;

    case PICT_r5g6b5:
        *tex_format = GL_RGB;
        *tex_type = GL_UNSIGNED_SHORT_5_6_5;
        break;
    case PICT_b5g6r5:
        *tex_format = GL_RGB;
        *tex_type = GL_UNSIGNED_SHORT_5_6_5_REV;
        break;
    case PICT_x1b5g5r5:
        *no_alpha = 1;
    case PICT_a1b5g5r5:
        *tex_format = GL_RGBA;
        *tex_type = GL_UNSIGNED_SHORT_1_5_5_5_REV;
        break;

    case PICT_x1r5g5b5:
        *no_alpha = 1;
    case PICT_a1r5g5b5:
        *tex_format = GL_BGRA;
        *tex_type = GL_UNSIGNED_SHORT_1_5_5_5_REV;
        break;
    case PICT_a8:
        *tex_format = GL_ALPHA;
        *tex_type = GL_UNSIGNED_BYTE;
        break;
    case PICT_x4r4g4b4:
        *no_alpha = 1;
    case PICT_a4r4g4b4:
        *tex_format = GL_BGRA;
        *tex_type = GL_UNSIGNED_SHORT_4_4_4_4_REV;
        break;

    case PICT_x4b4g4r4:
        *no_alpha = 1;
    case PICT_a4b4g4r4:
        *tex_format = GL_RGBA;
        *tex_type = GL_UNSIGNED_SHORT_4_4_4_4_REV;
        break;

    default:
        return -1;
    }
    return 0;
}

#define IS_LITTLE_ENDIAN  (IMAGE_BYTE_ORDER == LSBFirst)

static int
glamor_get_tex_format_type_from_pictformat_gles2(PictFormatShort format,
                                                 GLenum *tex_format,
                                                 GLenum *tex_type,
                                                 int *no_alpha,
                                                 int *revert,
                                                 int *swap_rb, int is_upload)
{
    int need_swap_rb = 0;

    *no_alpha = 0;
    *revert = IS_LITTLE_ENDIAN ? REVERT_NONE : REVERT_NORMAL;

    switch (format) {
    case PICT_b8g8r8x8:
        *no_alpha = 1;
    case PICT_b8g8r8a8:
        *tex_format = GL_RGBA;
        *tex_type = GL_UNSIGNED_BYTE;
        need_swap_rb = 1;
        *revert = IS_LITTLE_ENDIAN ? REVERT_NORMAL : REVERT_NONE;
        break;

    case PICT_x8r8g8b8:
        *no_alpha = 1;
    case PICT_a8r8g8b8:
        *tex_format = GL_RGBA;
        *tex_type = GL_UNSIGNED_BYTE;
        need_swap_rb = 1;
        break;

    case PICT_x8b8g8r8:
        *no_alpha = 1;
    case PICT_a8b8g8r8:
        *tex_format = GL_RGBA;
        *tex_type = GL_UNSIGNED_BYTE;
        break;

    case PICT_x2r10g10b10:
        *no_alpha = 1;
    case PICT_a2r10g10b10:
        *tex_format = GL_RGBA;
        /* glReadPixmap doesn't support GL_UNSIGNED_INT_10_10_10_2.
         * we have to use GL_UNSIGNED_BYTE and do the conversion in
         * shader latter.*/
        *tex_type = GL_UNSIGNED_BYTE;
        if (is_upload == 1) {
            if (!IS_LITTLE_ENDIAN)
                *revert = REVERT_UPLOADING_10_10_10_2;
            else
                *revert = REVERT_UPLOADING_2_10_10_10;
        }
        else {
            if (!IS_LITTLE_ENDIAN) {
                *revert = REVERT_DOWNLOADING_10_10_10_2;
            }
            else {
                *revert = REVERT_DOWNLOADING_2_10_10_10;
            }
        }
        need_swap_rb = 1;

        break;

    case PICT_x2b10g10r10:
        *no_alpha = 1;
    case PICT_a2b10g10r10:
        *tex_format = GL_RGBA;
        *tex_type = GL_UNSIGNED_BYTE;
        if (is_upload == 1) {
            if (!IS_LITTLE_ENDIAN)
                *revert = REVERT_UPLOADING_10_10_10_2;
            else
                *revert = REVERT_UPLOADING_2_10_10_10;
        }
        else {
            if (!IS_LITTLE_ENDIAN) {
                *revert = REVERT_DOWNLOADING_10_10_10_2;
            }
            else {
                *revert = REVERT_DOWNLOADING_2_10_10_10;
            }
        }
        break;

    case PICT_r5g6b5:
        *tex_format = GL_RGB;
        *tex_type = GL_UNSIGNED_SHORT_5_6_5;
        *revert = IS_LITTLE_ENDIAN ? REVERT_NONE : REVERT_NORMAL;

        break;

    case PICT_b5g6r5:
        *tex_format = GL_RGB;
        *tex_type = GL_UNSIGNED_SHORT_5_6_5;
        need_swap_rb = IS_LITTLE_ENDIAN ? 1 : 0;;
        break;

    case PICT_x1b5g5r5:
        *no_alpha = 1;
    case PICT_a1b5g5r5:
        *tex_format = GL_RGBA;
        *tex_type = GL_UNSIGNED_SHORT_5_5_5_1;
        if (IS_LITTLE_ENDIAN) {
            *revert =
                is_upload ? REVERT_UPLOADING_1_5_5_5 :
                REVERT_DOWNLOADING_1_5_5_5;
        }
        else
            *revert = REVERT_NONE;
        break;

    case PICT_x1r5g5b5:
        *no_alpha = 1;
    case PICT_a1r5g5b5:
        *tex_format = GL_RGBA;
        *tex_type = GL_UNSIGNED_SHORT_5_5_5_1;
        if (IS_LITTLE_ENDIAN) {
            *revert =
                is_upload ? REVERT_UPLOADING_1_5_5_5 :
                REVERT_DOWNLOADING_1_5_5_5;
        }
        else
            *revert = REVERT_NONE;
        need_swap_rb = 1;
        break;

    case PICT_a1:
        *tex_format = GL_ALPHA;
        *tex_type = GL_UNSIGNED_BYTE;
        *revert = is_upload ? REVERT_UPLOADING_A1 : REVERT_DOWNLOADING_A1;
        break;

    case PICT_a8:
        *tex_format = GL_ALPHA;
        *tex_type = GL_UNSIGNED_BYTE;
        *revert = REVERT_NONE;
        break;

    case PICT_x4r4g4b4:
        *no_alpha = 1;
    case PICT_a4r4g4b4:
        *tex_format = GL_RGBA;
        *tex_type = GL_UNSIGNED_SHORT_4_4_4_4;
        *revert = IS_LITTLE_ENDIAN ? REVERT_NORMAL : REVERT_NONE;
        need_swap_rb = 1;
        break;

    case PICT_x4b4g4r4:
        *no_alpha = 1;
    case PICT_a4b4g4r4:
        *tex_format = GL_RGBA;
        *tex_type = GL_UNSIGNED_SHORT_4_4_4_4;
        *revert = IS_LITTLE_ENDIAN ? REVERT_NORMAL : REVERT_NONE;
        break;

    default:
        LogMessageVerb(X_INFO, 0,
                       "fail to get matched format for %x \n", format);
        return -1;
    }

    if (need_swap_rb)
        *swap_rb = is_upload ? SWAP_UPLOADING : SWAP_DOWNLOADING;
    else
        *swap_rb = is_upload ? SWAP_NONE_UPLOADING : SWAP_NONE_DOWNLOADING;
    return 0;
}

static int
glamor_get_tex_format_type_from_pixmap(PixmapPtr pixmap,
                                       GLenum *format,
                                       GLenum *type,
                                       int *no_alpha,
                                       int *revert, int *swap_rb, int is_upload)
{
    glamor_pixmap_private *pixmap_priv;
    PictFormatShort pict_format;
    glamor_screen_private *glamor_priv =
        glamor_get_screen_private(pixmap->drawable.pScreen);

    pixmap_priv = glamor_get_pixmap_private(pixmap);
    if (GLAMOR_PIXMAP_PRIV_IS_PICTURE(pixmap_priv))
        pict_format = pixmap_priv->base.picture->format;
    else
        pict_format = format_for_depth(pixmap->drawable.depth);

    if (glamor_priv->gl_flavor == GLAMOR_GL_DESKTOP) {
        return glamor_get_tex_format_type_from_pictformat_gl(pict_format,
                                                             format, type,
                                                             no_alpha,
                                                             revert,
                                                             swap_rb,
                                                             is_upload);
    } else {
        return glamor_get_tex_format_type_from_pictformat_gles2(pict_format,
                                                                format, type,
                                                                no_alpha,
                                                                revert,
                                                                swap_rb,
                                                                is_upload);
    }
}

static void *
_glamor_color_convert_a1_a8(void *src_bits, void *dst_bits, int w, int h,
                            int stride, int revert)
{
    PictFormatShort dst_format, src_format;
    pixman_image_t *dst_image;
    pixman_image_t *src_image;
    int src_stride;

    if (revert == REVERT_UPLOADING_A1) {
        src_format = PICT_a1;
        dst_format = PICT_a8;
        src_stride = PixmapBytePad(w, 1);
    }
    else {
        dst_format = PICT_a1;
        src_format = PICT_a8;
        src_stride = (((w * 8 + 7) / 8) + 3) & ~3;
    }

    dst_image = pixman_image_create_bits(dst_format, w, h, dst_bits, stride);
    if (dst_image == NULL) {
        return NULL;
    }

    src_image = pixman_image_create_bits(src_format,
                                         w, h, src_bits, src_stride);

    if (src_image == NULL) {
        pixman_image_unref(dst_image);
        return NULL;
    }

    pixman_image_composite(PictOpSrc, src_image, NULL, dst_image,
                           0, 0, 0, 0, 0, 0, w, h);

    pixman_image_unref(src_image);
    pixman_image_unref(dst_image);
    return dst_bits;
}

#define ADJUST_BITS(d, src_bits, dst_bits)	(((dst_bits) == (src_bits)) ? (d) : 				\
							(((dst_bits) > (src_bits)) ? 				\
							  (((d) << ((dst_bits) - (src_bits))) 			\
								   + (( 1 << ((dst_bits) - (src_bits))) >> 1))	\
								:  ((d) >> ((src_bits) - (dst_bits)))))

#define GLAMOR_DO_CONVERT(src, dst, no_alpha, swap,		\
			  a_shift_src, a_bits_src,		\
			  b_shift_src, b_bits_src,		\
			  g_shift_src, g_bits_src,		\
			  r_shift_src, r_bits_src,		\
			  a_shift, a_bits,			\
			  b_shift, b_bits,			\
			  g_shift, g_bits,			\
			  r_shift, r_bits)			\
	do {								\
		typeof(src) a,b,g,r;					\
		typeof(src) a_mask_src, b_mask_src, g_mask_src, r_mask_src;\
		a_mask_src = (((1 << (a_bits_src)) - 1) << a_shift_src);\
		b_mask_src = (((1 << (b_bits_src)) - 1) << b_shift_src);\
		g_mask_src = (((1 << (g_bits_src)) - 1) << g_shift_src);\
		r_mask_src = (((1 << (r_bits_src)) - 1) << r_shift_src);\
		if (no_alpha)						\
			a = (a_mask_src) >> (a_shift_src);			\
		else							\
			a = ((src) & (a_mask_src)) >> (a_shift_src);	\
		b = ((src) & (b_mask_src)) >> (b_shift_src);		\
		g = ((src) & (g_mask_src)) >> (g_shift_src);		\
		r = ((src) & (r_mask_src)) >> (r_shift_src);		\
		a = ADJUST_BITS(a, a_bits_src, a_bits);			\
		b = ADJUST_BITS(b, b_bits_src, b_bits);			\
		g = ADJUST_BITS(g, g_bits_src, g_bits);			\
		r = ADJUST_BITS(r, r_bits_src, r_bits);			\
		if (swap == 0)						\
			(*dst) = ((a) << (a_shift)) | ((b) << (b_shift)) | ((g) << (g_shift)) | ((r) << (r_shift)); \
		else 												    \
			(*dst) = ((a) << (a_shift)) | ((r) << (b_shift)) | ((g) << (g_shift)) | ((b) << (r_shift)); \
	} while (0)

static void *
_glamor_color_revert_x2b10g10r10(void *src_bits, void *dst_bits, int w, int h,
                                 int stride, int no_alpha, int revert,
                                 int swap_rb)
{
    int x, y;
    unsigned int *words, *saved_words, *source_words;
    int swap = !(swap_rb == SWAP_NONE_DOWNLOADING ||
                 swap_rb == SWAP_NONE_UPLOADING);

    source_words = src_bits;
    words = dst_bits;
    saved_words = words;

    for (y = 0; y < h; y++) {
        DEBUGF("Line %d :  ", y);
        for (x = 0; x < w; x++) {
            unsigned int pixel = source_words[x];

            if (revert == REVERT_DOWNLOADING_2_10_10_10)
                GLAMOR_DO_CONVERT(pixel, &words[x], no_alpha, swap,
                                  24, 8, 16, 8, 8, 8, 0, 8,
                                  30, 2, 20, 10, 10, 10, 0, 10);
            else
                GLAMOR_DO_CONVERT(pixel, &words[x], no_alpha, swap,
                                  30, 2, 20, 10, 10, 10, 0, 10,
                                  24, 8, 16, 8, 8, 8, 0, 8);
            DEBUGF("%x:%x ", pixel, words[x]);
        }
        DEBUGF("\n");
        words += stride / sizeof(*words);
        source_words += stride / sizeof(*words);
    }
    DEBUGF("\n");
    return saved_words;

}

static void *
_glamor_color_revert_x1b5g5r5(void *src_bits, void *dst_bits, int w, int h,
                              int stride, int no_alpha, int revert, int swap_rb)
{
    int x, y;
    unsigned short *words, *saved_words, *source_words;
    int swap = !(swap_rb == SWAP_NONE_DOWNLOADING ||
                 swap_rb == SWAP_NONE_UPLOADING);

    words = dst_bits;
    source_words = src_bits;
    saved_words = words;

    for (y = 0; y < h; y++) {
        DEBUGF("Line %d :  ", y);
        for (x = 0; x < w; x++) {
            unsigned short pixel = source_words[x];

            if (revert == REVERT_DOWNLOADING_1_5_5_5)
                GLAMOR_DO_CONVERT(pixel, &words[x], no_alpha, swap,
                                  0, 1, 1, 5, 6, 5, 11, 5,
                                  15, 1, 10, 5, 5, 5, 0, 5);
            else
                GLAMOR_DO_CONVERT(pixel, &words[x], no_alpha, swap,
                                  15, 1, 10, 5, 5, 5, 0, 5,
                                  0, 1, 1, 5, 6, 5, 11, 5);
            DEBUGF("%04x:%04x ", pixel, words[x]);
        }
        DEBUGF("\n");
        words += stride / sizeof(*words);
        source_words += stride / sizeof(*words);
    }
    DEBUGF("\n");
    return saved_words;
}

/*
 * This function is to convert an unsupported color format to/from a
 * supported GL format.
 * Here are the current scenarios:
 *
 * @no_alpha:
 * 	If it is set, then we need to wire the alpha value to 1.
 * @revert:
	REVERT_DOWNLOADING_A1		: convert an Alpha8 buffer to a A1 buffer.
	REVERT_UPLOADING_A1		: convert an A1 buffer to an Alpha8 buffer
	REVERT_DOWNLOADING_2_10_10_10 	: convert r10G10b10X2 to X2B10G10R10
	REVERT_UPLOADING_2_10_10_10 	: convert X2B10G10R10 to R10G10B10X2
	REVERT_DOWNLOADING_1_5_5_5  	: convert B5G5R5X1 to X1R5G5B5
	REVERT_UPLOADING_1_5_5_5    	: convert X1R5G5B5 to B5G5R5X1
   @swap_rb: if we have the swap_rb set, then we need to swap the R and B's position.
 *
 */

static void *
glamor_color_convert_to_bits(void *src_bits, void *dst_bits, int w, int h,
                             int stride, int no_alpha, int revert, int swap_rb)
{
    if (revert == REVERT_DOWNLOADING_A1 || revert == REVERT_UPLOADING_A1) {
        return _glamor_color_convert_a1_a8(src_bits, dst_bits, w, h, stride,
                                           revert);
    }
    else if (revert == REVERT_DOWNLOADING_2_10_10_10 ||
             revert == REVERT_UPLOADING_2_10_10_10) {
        return _glamor_color_revert_x2b10g10r10(src_bits, dst_bits, w, h,
                                                stride, no_alpha, revert,
                                                swap_rb);
    }
    else if (revert == REVERT_DOWNLOADING_1_5_5_5 ||
             revert == REVERT_UPLOADING_1_5_5_5) {
        return _glamor_color_revert_x1b5g5r5(src_bits, dst_bits, w, h, stride,
                                             no_alpha, revert, swap_rb);
    }
    else
        ErrorF("convert a non-supported mode %x.\n", revert);

    return NULL;
}

/**
 * Upload pixmap to a specified texture.
 * This texture may not be the one attached to it.
 **/
static void
__glamor_upload_pixmap_to_texture(PixmapPtr pixmap, unsigned int *tex,
                                  GLenum format,
                                  GLenum type,
                                  int x, int y, int w, int h,
                                  void *bits, int pbo)
{
    glamor_screen_private *glamor_priv =
        glamor_get_screen_private(pixmap->drawable.pScreen);
    int non_sub = 0;
    unsigned int iformat = 0;

    glamor_make_current(glamor_priv);
    if (*tex == 0) {
        glGenTextures(1, tex);
        if (glamor_priv->gl_flavor == GLAMOR_GL_DESKTOP)
            iformat = gl_iformat_for_pixmap(pixmap);
        else
            iformat = format;
        non_sub = 1;
        assert(x == 0 && y == 0);
    }

    glBindTexture(GL_TEXTURE_2D, *tex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

    assert(pbo || bits != 0);
    if (bits == NULL) {
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
        glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
    }
    if (non_sub)
        glTexImage2D(GL_TEXTURE_2D, 0, iformat, w, h, 0, format, type, bits);
    else
        glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, format, type, bits);

    if (bits == NULL)
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}

static Bool
_glamor_upload_bits_to_pixmap_texture(PixmapPtr pixmap, GLenum format,
                                      GLenum type, int no_alpha, int revert,
                                      int swap_rb, int x, int y, int w, int h,
                                      int stride, void *bits, int pbo)
{
    glamor_pixmap_private *pixmap_priv = glamor_get_pixmap_private(pixmap);
    glamor_screen_private *glamor_priv =
        glamor_get_screen_private(pixmap->drawable.pScreen);
    static float vertices[8];

    static float texcoords_inv[8] = { 0, 0,
        1, 0,
        1, 1,
        0, 1
    };
    float *ptexcoords;
    float dst_xscale, dst_yscale;
    GLuint tex = 0;
    int need_free_bits = 0;

    if (bits == NULL)
        goto ready_to_upload;

    if (revert > REVERT_NORMAL) {
        /* XXX if we are restoring the pixmap, then we may not need to allocate
         * new buffer */
        void *converted_bits;

        if (pixmap->drawable.depth == 1)
            stride = (((w * 8 + 7) / 8) + 3) & ~3;

        converted_bits = malloc(h * stride);

        if (converted_bits == NULL)
            return FALSE;
        bits = glamor_color_convert_to_bits(bits, converted_bits, w, h,
                                            stride, no_alpha, revert, swap_rb);
        if (bits == NULL) {
            free(converted_bits);
            ErrorF("Failed to convert pixmap no_alpha %d,"
                   "revert mode %d, swap mode %d\n", no_alpha, revert, swap_rb);
            return FALSE;
        }
        no_alpha = 0;
        revert = REVERT_NONE;
        swap_rb = SWAP_NONE_UPLOADING;
        need_free_bits = TRUE;
    }

 ready_to_upload:

    /* Try fast path firstly, upload the pixmap to the texture attached
     * to the fbo directly. */
    if (no_alpha == 0
        && revert == REVERT_NONE && swap_rb == SWAP_NONE_UPLOADING
#ifdef WALKAROUND_LARGE_TEXTURE_MAP
        && pixmap_priv->type != GLAMOR_TEXTURE_LARGE
#endif
        ) {
        int fbo_x_off, fbo_y_off;

        assert(pixmap_priv->base.fbo->tex);
        pixmap_priv_get_fbo_off(pixmap_priv, &fbo_x_off, &fbo_y_off);

        assert(x + fbo_x_off >= 0 && y + fbo_y_off >= 0);
        assert(x + fbo_x_off + w <= pixmap_priv->base.fbo->width);
        assert(y + fbo_y_off + h <= pixmap_priv->base.fbo->height);
        __glamor_upload_pixmap_to_texture(pixmap, &pixmap_priv->base.fbo->tex,
                                          format, type,
                                          x + fbo_x_off, y + fbo_y_off, w, h,
                                          bits, pbo);
    } else {
        ptexcoords = texcoords_inv;

        pixmap_priv_get_dest_scale(pixmap_priv, &dst_xscale, &dst_yscale);
        glamor_set_normalize_vcoords(pixmap_priv, dst_xscale,
                                     dst_yscale,
                                     x, y,
                                     x + w, y + h,
                                     vertices);
        /* Slow path, we need to flip y or wire alpha to 1. */
        glamor_make_current(glamor_priv);
        glVertexAttribPointer(GLAMOR_VERTEX_POS, 2, GL_FLOAT,
                              GL_FALSE, 2 * sizeof(float), vertices);
        glEnableVertexAttribArray(GLAMOR_VERTEX_POS);
        glVertexAttribPointer(GLAMOR_VERTEX_SOURCE, 2, GL_FLOAT,
                              GL_FALSE, 2 * sizeof(float), ptexcoords);
        glEnableVertexAttribArray(GLAMOR_VERTEX_SOURCE);

        glamor_set_destination_pixmap_priv_nc(pixmap_priv);
        __glamor_upload_pixmap_to_texture(pixmap, &tex,
                                          format, type, 0, 0, w, h, bits, pbo);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glUseProgram(glamor_priv->finish_access_prog[no_alpha]);
        glUniform1i(glamor_priv->finish_access_revert[no_alpha], revert);
        glUniform1i(glamor_priv->finish_access_swap_rb[no_alpha], swap_rb);

        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

        glDisableVertexAttribArray(GLAMOR_VERTEX_POS);
        glDisableVertexAttribArray(GLAMOR_VERTEX_SOURCE);
        glDeleteTextures(1, &tex);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    if (need_free_bits)
        free(bits);
    return TRUE;
}

/*
 * Prepare to upload a pixmap to texture memory.
 * no_alpha equals 1 means the format needs to wire alpha to 1.
 */
static int
glamor_pixmap_upload_prepare(PixmapPtr pixmap, GLenum format, int no_alpha,
                             int revert, int swap_rb)
{
    int flag = 0;
    glamor_pixmap_private *pixmap_priv;
    glamor_screen_private *glamor_priv;
    glamor_pixmap_fbo *fbo;
    GLenum iformat;

    pixmap_priv = glamor_get_pixmap_private(pixmap);
    glamor_priv = glamor_get_screen_private(pixmap->drawable.pScreen);

    if (pixmap_priv->base.gl_fbo != GLAMOR_FBO_UNATTACHED)
        return 0;

    if (pixmap_priv->base.fbo
        && (pixmap_priv->base.fbo->width < pixmap->drawable.width
            || pixmap_priv->base.fbo->height < pixmap->drawable.height)) {
        fbo = glamor_pixmap_detach_fbo(pixmap_priv);
        glamor_destroy_fbo(fbo);
    }

    if (pixmap_priv->base.fbo && pixmap_priv->base.fbo->fb)
        return 0;

    if (!(no_alpha || (revert == REVERT_NORMAL)
          || (swap_rb != SWAP_NONE_UPLOADING))) {
        /* We don't need a fbo, a simple texture uploading should work. */

        flag = GLAMOR_CREATE_FBO_NO_FBO;
    }

    if ((flag == GLAMOR_CREATE_FBO_NO_FBO
         && pixmap_priv->base.fbo && pixmap_priv->base.fbo->tex)
        || (flag == 0 && pixmap_priv->base.fbo && pixmap_priv->base.fbo->fb))
        return 0;

    if (glamor_priv->gl_flavor == GLAMOR_GL_DESKTOP)
        iformat = gl_iformat_for_pixmap(pixmap);
    else
        iformat = format;

    if (!glamor_pixmap_ensure_fbo(pixmap, iformat, flag))
        return -1;

    return 0;
}

/*
 * upload sub region to a large region.
 * */
static void
glamor_put_bits(char *dst_bits, int dst_stride, char *src_bits,
                int src_stride, int bpp, int x, int y, int w, int h)
{
    int j;
    int byte_per_pixel;

    byte_per_pixel = bpp / 8;
    src_bits += y * src_stride + (x * byte_per_pixel);

    for (j = y; j < y + h; j++) {
        memcpy(dst_bits, src_bits, w * byte_per_pixel);
        src_bits += src_stride;
        dst_bits += dst_stride;
    }
}

Bool
glamor_upload_sub_pixmap_to_texture(PixmapPtr pixmap, int x, int y, int w,
                                    int h, int stride, void *bits, int pbo)
{
    GLenum format, type;
    int no_alpha, revert, swap_rb;
    glamor_pixmap_private *pixmap_priv;
    Bool force_clip;

    if (glamor_get_tex_format_type_from_pixmap(pixmap,
                                               &format,
                                               &type,
                                               &no_alpha,
                                               &revert, &swap_rb, 1)) {
        glamor_fallback("Unknown pixmap depth %d.\n", pixmap->drawable.depth);
        return FALSE;
    }
    if (glamor_pixmap_upload_prepare(pixmap, format, no_alpha, revert, swap_rb))
        return FALSE;

    pixmap_priv = glamor_get_pixmap_private(pixmap);
    force_clip = pixmap_priv->base.glamor_priv->gl_flavor != GLAMOR_GL_DESKTOP
        && !glamor_check_fbo_size(pixmap_priv->base.glamor_priv, w, h);

    if (pixmap_priv->type == GLAMOR_TEXTURE_LARGE || force_clip) {
        RegionRec region;
        BoxRec box;
        int n_region;
        glamor_pixmap_clipped_regions *clipped_regions;
        void *sub_bits;
        int i, j;

        sub_bits = malloc(h * stride);
        if (sub_bits == NULL)
            return FALSE;
        box.x1 = x;
        box.y1 = y;
        box.x2 = x + w;
        box.y2 = y + h;
        RegionInitBoxes(&region, &box, 1);
        if (!force_clip)
            clipped_regions =
                glamor_compute_clipped_regions(pixmap_priv, &region, &n_region,
                                               0, 0, 0);
        else
            clipped_regions =
                glamor_compute_clipped_regions_ext(pixmap_priv, &region,
                                                   &n_region,
                                                   pixmap_priv->large.block_w,
                                                   pixmap_priv->large.block_h,
                                                   0,
                                                   0);
        DEBUGF("prepare upload %dx%d to a large pixmap %p\n", w, h, pixmap);
        for (i = 0; i < n_region; i++) {
            BoxPtr boxes;
            int nbox;
            int temp_stride;
            void *temp_bits;

            assert(pbo == 0);

            glamor_set_pixmap_fbo_current(pixmap_priv, clipped_regions[i].block_idx);

            boxes = RegionRects(clipped_regions[i].region);
            nbox = RegionNumRects(clipped_regions[i].region);
            DEBUGF("split to %d boxes\n", nbox);
            for (j = 0; j < nbox; j++) {
                temp_stride = PixmapBytePad(boxes[j].x2 - boxes[j].x1,
                                            pixmap->drawable.depth);

                if (boxes[j].x1 == x && temp_stride == stride) {
                    temp_bits = (char *) bits + (boxes[j].y1 - y) * stride;
                }
                else {
                    temp_bits = sub_bits;
                    glamor_put_bits(temp_bits, temp_stride, bits, stride,
                                    pixmap->drawable.bitsPerPixel,
                                    boxes[j].x1 - x, boxes[j].y1 - y,
                                    boxes[j].x2 - boxes[j].x1,
                                    boxes[j].y2 - boxes[j].y1);
                }
                DEBUGF("upload x %d y %d w %d h %d temp stride %d \n",
                       boxes[j].x1 - x, boxes[j].y1 - y,
                       boxes[j].x2 - boxes[j].x1,
                       boxes[j].y2 - boxes[j].y1, temp_stride);
                if (_glamor_upload_bits_to_pixmap_texture
                    (pixmap, format, type, no_alpha, revert, swap_rb,
                     boxes[j].x1, boxes[j].y1, boxes[j].x2 - boxes[j].x1,
                     boxes[j].y2 - boxes[j].y1, temp_stride, temp_bits,
                     pbo) == FALSE) {
                    RegionUninit(&region);
                    free(sub_bits);
                    assert(0);
                    return FALSE;
                }
            }
            RegionDestroy(clipped_regions[i].region);
        }
        free(sub_bits);
        free(clipped_regions);
        RegionUninit(&region);
        return TRUE;
    }
    else
        return _glamor_upload_bits_to_pixmap_texture(pixmap, format, type,
                                                     no_alpha, revert, swap_rb,
                                                     x, y, w, h, stride, bits,
                                                     pbo);
}

enum glamor_pixmap_status
glamor_upload_pixmap_to_texture(PixmapPtr pixmap)
{
    glamor_pixmap_private *pixmap_priv;
    void *data;
    int pbo;
    int ret;

    pixmap_priv = glamor_get_pixmap_private(pixmap);

    if ((pixmap_priv->base.fbo)
        && (pixmap_priv->base.fbo->pbo_valid)) {
        data = NULL;
        pbo = pixmap_priv->base.fbo->pbo;
    }
    else {
        data = pixmap->devPrivate.ptr;
        pbo = 0;
    }

    if (glamor_upload_sub_pixmap_to_texture(pixmap, 0, 0,
                                            pixmap->drawable.width,
                                            pixmap->drawable.height,
                                            pixmap->devKind, data, pbo))
        ret = GLAMOR_UPLOAD_DONE;
    else
        ret = GLAMOR_UPLOAD_FAILED;

    return ret;
}

/*
 * as gles2 only support a very small set of color format and
 * type when do glReadPixel,
 * Before we use glReadPixels to get back a textured pixmap,
 * Use shader to convert it to a supported format and thus
 * get a new temporary pixmap returned.
 * */

glamor_pixmap_fbo *
glamor_es2_pixmap_read_prepare(PixmapPtr source, int x, int y, int w, int h,
                               GLenum format, GLenum type, int no_alpha,
                               int revert, int swap_rb)
{
    glamor_pixmap_private *source_priv;
    glamor_screen_private *glamor_priv;
    ScreenPtr screen;
    glamor_pixmap_fbo *temp_fbo;
    float temp_xscale, temp_yscale, source_xscale, source_yscale;
    static float vertices[8];
    static float texcoords[8];

    screen = source->drawable.pScreen;

    glamor_priv = glamor_get_screen_private(screen);
    source_priv = glamor_get_pixmap_private(source);
    temp_fbo = glamor_create_fbo(glamor_priv, w, h, format, 0);
    if (temp_fbo == NULL)
        return NULL;

    glamor_make_current(glamor_priv);
    temp_xscale = 1.0 / w;
    temp_yscale = 1.0 / h;

    glamor_set_normalize_vcoords((struct glamor_pixmap_private *) NULL,
                                 temp_xscale, temp_yscale, 0, 0, w, h,
                                 vertices);

    glVertexAttribPointer(GLAMOR_VERTEX_POS, 2, GL_FLOAT, GL_FALSE,
                          2 * sizeof(float), vertices);
    glEnableVertexAttribArray(GLAMOR_VERTEX_POS);

    pixmap_priv_get_scale(source_priv, &source_xscale, &source_yscale);
    glamor_set_normalize_tcoords(source_priv, source_xscale,
                                 source_yscale,
                                 x, y,
                                 x + w, y + h,
                                 texcoords);

    glVertexAttribPointer(GLAMOR_VERTEX_SOURCE, 2, GL_FLOAT, GL_FALSE,
                          2 * sizeof(float), texcoords);
    glEnableVertexAttribArray(GLAMOR_VERTEX_SOURCE);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, source_priv->base.fbo->tex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    glamor_set_destination_pixmap_fbo(temp_fbo, 0, 0, w, h);
    glUseProgram(glamor_priv->finish_access_prog[no_alpha]);
    glUniform1i(glamor_priv->finish_access_revert[no_alpha], revert);
    glUniform1i(glamor_priv->finish_access_swap_rb[no_alpha], swap_rb);

    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

    glDisableVertexAttribArray(GLAMOR_VERTEX_POS);
    glDisableVertexAttribArray(GLAMOR_VERTEX_SOURCE);
    return temp_fbo;
}

/* fixup a fbo to the exact size as the pixmap. */
/* XXX LARGE pixmap? */
Bool
glamor_fixup_pixmap_priv(ScreenPtr screen, glamor_pixmap_private *pixmap_priv)
{
    glamor_pixmap_fbo *old_fbo;
    glamor_pixmap_fbo *new_fbo = NULL;
    PixmapPtr scratch = NULL;
    glamor_pixmap_private *scratch_priv;
    DrawablePtr drawable;
    GCPtr gc = NULL;
    int ret = FALSE;

    drawable = &pixmap_priv->base.pixmap->drawable;

    if (!GLAMOR_PIXMAP_FBO_NOT_EXACT_SIZE(pixmap_priv))
        return TRUE;

    old_fbo = pixmap_priv->base.fbo;

    if (!old_fbo)
        return FALSE;

    gc = GetScratchGC(drawable->depth, screen);
    if (!gc)
        goto fail;

    scratch = glamor_create_pixmap(screen, drawable->width, drawable->height,
                                   drawable->depth, GLAMOR_CREATE_PIXMAP_FIXUP);

    scratch_priv = glamor_get_pixmap_private(scratch);

    if (!scratch_priv->base.fbo)
        goto fail;

    ValidateGC(&scratch->drawable, gc);
    glamor_copy_area(drawable,
                     &scratch->drawable,
                     gc, 0, 0, drawable->width, drawable->height, 0, 0);
    old_fbo = glamor_pixmap_detach_fbo(pixmap_priv);
    new_fbo = glamor_pixmap_detach_fbo(scratch_priv);
    glamor_pixmap_attach_fbo(pixmap_priv->base.pixmap, new_fbo);
    glamor_pixmap_attach_fbo(scratch, old_fbo);

    DEBUGF("old %dx%d type %d\n",
           drawable->width, drawable->height, pixmap_priv->type);
    DEBUGF("copy tex %d  %dx%d to tex %d %dx%d \n",
           old_fbo->tex, old_fbo->width, old_fbo->height, new_fbo->tex,
           new_fbo->width, new_fbo->height);
    ret = TRUE;
 fail:
    if (gc)
        FreeScratchGC(gc);
    if (scratch)
        glamor_destroy_pixmap(scratch);

    return ret;
}
