/*
 * Copyright 2011 VMWare, Inc.
 * All Rights Reserved.
 *
 * 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, sub license, 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 NON-INFRINGEMENT.
 * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS 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.
 *
 * Author: Thomas Hellstrom <thellstrom@vmware.com>
 */
#ifdef _HAVE_CONFIG_H_
#include "config.h"
#endif

#include <xorg-server.h>
#include "vmwgfx_saa_priv.h"


static const enum xa_surface_type vmwgfx_stype_map[] = {
  [PICT_TYPE_OTHER] = xa_type_other,
  [PICT_TYPE_A] = xa_type_a,
  [PICT_TYPE_ARGB] = xa_type_argb,
  [PICT_TYPE_ABGR] = xa_type_abgr,
  [PICT_TYPE_BGRA] = xa_type_bgra
};

static const unsigned int vmwgfx_stype_map_size =
    sizeof(vmwgfx_stype_map) / sizeof(enum xa_surface_type);

/**
 * vmwgfx_xa_surface_redefine - wrapper around xa_surface_redefine
 *
 * @vpix: Pointer to the struct vmwgfx_saa_pixmap the surface is attached to.
 * @srf: The surface.
 * @width: New width.
 * @height: New height.
 * @depth: New pixel depth.
 * @stype: New surface type.
 * @rgb_format: New rgb format.
 * @new_flags: New surface flags.
 * @copy_contents: Copy contents if new backing store is allocated.
 *
 * This is a wrapper that prints out an error message if the backing store
 * of an active scanout surface is changed.
 */
Bool
vmwgfx_xa_surface_redefine(struct vmwgfx_saa_pixmap *vpix,
			   struct xa_surface *srf,
			   int width,
			   int height,
			   int depth,
			   enum xa_surface_type stype,
			   enum xa_formats rgb_format,
			   unsigned int new_flags,
			   int copy_contents)
{
    uint32_t handle, new_handle, dummy;
    Bool have_handle = FALSE;

    if (!WSBMLISTEMPTY(&vpix->scanout_list))
	have_handle = (_xa_surface_handle(srf, &handle, &dummy) == XA_ERR_NONE);

    if (xa_surface_redefine(srf, width, height, depth, stype, rgb_format,
			    new_flags, copy_contents) != XA_ERR_NONE)
	return FALSE;

    if (!WSBMLISTEMPTY(&vpix->scanout_list) && have_handle &&
	_xa_surface_handle(srf, &new_handle, &dummy) == XA_ERR_NONE &&
	new_handle != handle) {
	LogMessage(X_ERROR, "Changed active scanout surface handle.\n");
    }

    return TRUE;
}


/*
 * Create an xa format from a PICT format.
 */
enum xa_formats
vmwgfx_xa_format(enum _PictFormatShort format)
{
    uint32_t ptype = PICT_FORMAT_TYPE(format);

    if (ptype >= vmwgfx_stype_map_size ||
	vmwgfx_stype_map[ptype] == 0 ||
	vmwgfx_stype_map[ptype] == xa_type_other)
	return xa_format_unknown;

    return xa_format(PICT_FORMAT_BPP(format),
		     vmwgfx_stype_map[ptype],
		     PICT_FORMAT_A(format),
		     PICT_FORMAT_R(format),
		     PICT_FORMAT_G(format),
		     PICT_FORMAT_B(format));
}

/*
 * Choose formats and flags for a dri2 surface.
 */
Bool
vmwgfx_hw_dri2_stage(PixmapPtr pixmap, unsigned int depth)
{
    struct vmwgfx_saa_pixmap *vpix = vmwgfx_saa_pixmap(pixmap);
    enum xa_formats format;

    if (depth == 0)
	depth = pixmap->drawable.depth;

    switch(depth) {
    case 32:
	format = xa_format_a8r8g8b8;
	break;
    case 24:
        format = xa_format_x8r8g8b8;
	break;
    case 16:
	format = xa_format_r5g6b5;
	break;
    case 15:
	format = xa_format_x1r5g5b5;
	break;
    default:
	return FALSE;
    }

    vpix->staging_format = format;
    vpix->staging_remove_flags = 0;
    vpix->staging_add_flags = XA_FLAG_RENDER_TARGET | XA_FLAG_SHARED;

    return TRUE;
}

/*
 * Is composite old format compatible? Only difference is that old format
 * has more alpha bits?
 */
static inline Bool
vmwgfx_old_format_compatible(enum xa_formats format,
			     enum xa_formats old_format)
{
    return (format == old_format ||
	    (xa_format_type(format) == xa_format_type(old_format) &&
	     xa_format_a(format) <= xa_format_a(old_format) &&
	     xa_format_r(format) == xa_format_r(old_format) &&
	     xa_format_g(format) == xa_format_g(old_format) &&
	     xa_format_b(format) == xa_format_b(old_format)));
}


/*
 * Choose format and flags for a composite dst surface.
 */
Bool
vmwgfx_hw_composite_dst_stage(PixmapPtr pixmap,
			      enum _PictFormatShort pict_format)
{
    struct vmwgfx_saa *vsaa =
	to_vmwgfx_saa(saa_get_driver(pixmap->drawable.pScreen));
    struct vmwgfx_saa_pixmap *vpix = vmwgfx_saa_pixmap(pixmap);
    enum xa_formats format = vmwgfx_xa_format(pict_format);

    /*
     * Check if we can reuse old hardware format.
     */
    if (vpix->hw) {
	enum xa_formats old_format = xa_surface_format(vpix->hw);

	if (vmwgfx_old_format_compatible(format, old_format))
	    format = old_format;
    }

    if (xa_format_check_supported(vsaa->xat, format,
				  vpix->xa_flags | XA_FLAG_RENDER_TARGET) !=
	XA_ERR_NONE) {
	return FALSE;
    }

    vpix->staging_format = format;
    vpix->staging_remove_flags = 0;
    vpix->staging_add_flags = XA_FLAG_RENDER_TARGET | XA_FLAG_SHARED;

    return TRUE;
}

/*
 * Choose format and flags for a composite src surface.
 */
Bool
vmwgfx_hw_composite_src_stage(PixmapPtr pixmap,
			      enum _PictFormatShort pict_format)
{
    struct vmwgfx_saa *vsaa =
	to_vmwgfx_saa(saa_get_driver(pixmap->drawable.pScreen));
    struct vmwgfx_saa_pixmap *vpix = vmwgfx_saa_pixmap(pixmap);
    enum xa_formats format = vmwgfx_xa_format(pict_format);
    enum xa_formats swizzle_format = xa_format_unknown;
    enum xa_surface_type ftype;

    if (format == xa_format_unknown)
	return FALSE;

    ftype = xa_format_type(format);
    if (ftype == xa_type_abgr) {

	swizzle_format = xa_format(xa_format_bpp(format),
				   xa_type_argb,
				   xa_format_a(format),
				   xa_format_r(format),
				   xa_format_g(format),
				   xa_format_b(format));
    }

    /*
     * Check if we can reuse old format.
     */

    if (vpix->hw) {
	enum xa_formats old_format = xa_surface_format(vpix->hw);

	if (vmwgfx_old_format_compatible(format, old_format) ||
	    (swizzle_format != xa_format_unknown &&
	     vmwgfx_old_format_compatible(swizzle_format, old_format))) {
	    format = old_format;
	    goto have_format;
	}
    }

    if (swizzle_format != xa_format_unknown &&
	xa_format_check_supported(vsaa->xat, swizzle_format, vpix->xa_flags) ==
	XA_ERR_NONE) {
	format = swizzle_format;
	goto have_format;
    }

    if (xa_format_check_supported(vsaa->xat, format, vpix->xa_flags) ==
	XA_ERR_NONE) {
	goto have_format;
    }

    return FALSE;
  have_format:
    vpix->staging_format = format;
    vpix->staging_remove_flags = 0;
    vpix->staging_add_flags = 0;

    return TRUE;
}

/*
 * Choose accel format given depth.
 */
static enum xa_formats
vmwgfx_choose_accel_format(unsigned int depth)
{
    switch(depth) {
    case 32:
	return xa_format_a8r8g8b8;
    case 24:
	return xa_format_x8r8g8b8;
    case 16:
	return xa_format_r5g6b5;
    case 15:
	return xa_format_x1r5g5b5;
    case 8:
	return xa_format_a8;
    default:
	break;
    }
    return xa_format_unknown;
}


/*
 * Determine xa format and flags for an ordinary accel surface.
 */
Bool
vmwgfx_hw_accel_stage(PixmapPtr pixmap, unsigned int depth,
		      uint32_t add_flags, uint32_t remove_flags)
{
    struct vmwgfx_saa_pixmap *vpix = vmwgfx_saa_pixmap(pixmap);
    enum xa_formats format = xa_format_unknown;

    if (depth == 0)
	depth = pixmap->drawable.depth;

    if (vpix->hw) {
	enum xa_formats old_format = xa_surface_format(vpix->hw);
	enum xa_surface_type ftype = xa_format_type(old_format);

	if (ftype != xa_type_argb &&
	    ftype != xa_type_a) {
	    LogMessage(X_ERROR,
		       "Acceleration fallback due to strange hw format.\n");
	    return FALSE;
	}

	if (xa_format_depth(old_format) == depth ||
	    (xa_format_depth(old_format) == 32 &&
	     depth == 24))
	    format = old_format;
    }

    if (format == xa_format_unknown)
	format = vmwgfx_choose_accel_format(depth);

    if (format == xa_format_unknown)
	return FALSE;

    vpix->staging_add_flags = add_flags;
    vpix->staging_remove_flags = remove_flags;
    vpix->staging_format = format;

    return TRUE;
}

/*
 * Create a surface with a format and flags determined by one of
 * the staging functions.
 */
Bool
vmwgfx_hw_commit(PixmapPtr pixmap)
{
    struct vmwgfx_saa *vsaa =
	to_vmwgfx_saa(saa_get_driver(pixmap->drawable.pScreen));
    struct saa_pixmap *spix = saa_get_saa_pixmap(pixmap);
    struct vmwgfx_saa_pixmap *vpix = to_vmwgfx_saa_pixmap(spix);
    enum xa_formats format = vpix->staging_format;

    if (vpix->hw) {
	enum xa_formats old_format = xa_surface_format(vpix->hw);

	if (vpix->staging_format != old_format) {
	    if (xa_format_type(format) != xa_format_type(old_format) ||
		xa_format_r(format) != xa_format_r(old_format) ||
		xa_format_g(format) != xa_format_g(old_format) ||
		xa_format_b(format) != xa_format_b(old_format)) {

		LogMessage(X_INFO, "Killing old hw surface.\n");

		if (!vmwgfx_hw_kill(vsaa, spix))
		    return FALSE;
	    }
	}
    }

    if (vpix->hw) {
	uint32_t new_flags;

	new_flags = (vpix->xa_flags & ~vpix->staging_remove_flags) |
	    vpix->staging_add_flags | XA_FLAG_SHARED;

	if (vpix->staging_format != xa_surface_format(vpix->hw))
	    LogMessage(X_INFO, "Changing hardware format.\n");

	if (!vmwgfx_xa_surface_redefine(vpix,
					vpix->hw,
					pixmap->drawable.width,
					pixmap->drawable.height,
					0,
					xa_type_other,
					vpix->staging_format,
					new_flags, 1) != XA_ERR_NONE)
	    return FALSE;
	vpix->xa_flags = new_flags;
    } else if (!vmwgfx_create_hw(vsaa, pixmap, FALSE))
	return FALSE;

    return TRUE;
}

/*
 * Create an accel surface if there is none, and make sure the region
 * given by @region is valid. If @region is NULL, the whole surface
 * will be valid. This is a utility convenience function only.
 */
Bool
vmwgfx_hw_accel_validate(PixmapPtr pixmap, unsigned int depth,
			 uint32_t add_flags, uint32_t remove_flags,
			 RegionPtr region)
{
    return (vmwgfx_hw_accel_stage(pixmap, depth, add_flags, remove_flags) &&
	    vmwgfx_hw_commit(pixmap) &&
	    vmwgfx_hw_validate(pixmap, region));
}


/*
 * Create a dri2 surface if there is none,
 * and make sure the whole surfade is valid.
 * This is a utility convenience function only.
 */
Bool
vmwgfx_hw_dri2_validate(PixmapPtr pixmap, unsigned int depth)
{
    struct vmwgfx_saa *vsaa =
	to_vmwgfx_saa(saa_get_driver(pixmap->drawable.pScreen));

    if (!vsaa->is_master)
	    return FALSE;

    return (vmwgfx_hw_dri2_stage(pixmap, depth) &&
	    vmwgfx_hw_commit(pixmap) &&
	    vmwgfx_hw_validate(pixmap, NULL));
}
