#include "xorg_composite.h"

#include "xorg_renderer.h"
#include "xorg_exa_tgsi.h"

#include "cso_cache/cso_context.h"
#include "util/u_sampler.h"


/*XXX also in Xrender.h but the including it here breaks compilition */
#define XFixedToDouble(f)    (((double) (f)) / 65536.)

struct xorg_composite_blend {
   int op : 8;

   unsigned alpha_dst : 4;
   unsigned alpha_src : 4;

   unsigned rgb_src : 8;    /**< PIPE_BLENDFACTOR_x */
   unsigned rgb_dst : 8;    /**< PIPE_BLENDFACTOR_x */
};

#define BLEND_OP_OVER 3
static const struct xorg_composite_blend xorg_blends[] = {
   { PictOpClear,
     0, 0, PIPE_BLENDFACTOR_ZERO, PIPE_BLENDFACTOR_ZERO},
   { PictOpSrc,
     0, 0, PIPE_BLENDFACTOR_ONE, PIPE_BLENDFACTOR_ZERO},
   { PictOpDst,
     0, 0, PIPE_BLENDFACTOR_ZERO, PIPE_BLENDFACTOR_ONE},
   { PictOpOver,
     0, 1, PIPE_BLENDFACTOR_ONE, PIPE_BLENDFACTOR_INV_SRC_ALPHA},
   { PictOpOverReverse,
     1, 0, PIPE_BLENDFACTOR_INV_DST_ALPHA, PIPE_BLENDFACTOR_ONE},
   { PictOpIn,
     1, 0, PIPE_BLENDFACTOR_DST_ALPHA, PIPE_BLENDFACTOR_ZERO},
   { PictOpInReverse,
     0, 1, PIPE_BLENDFACTOR_ZERO, PIPE_BLENDFACTOR_SRC_ALPHA},
   { PictOpOut,
     1, 0, PIPE_BLENDFACTOR_INV_DST_ALPHA, PIPE_BLENDFACTOR_ZERO},
   { PictOpOutReverse,
     0, 1, PIPE_BLENDFACTOR_ZERO, PIPE_BLENDFACTOR_INV_SRC_ALPHA},
   { PictOpAtop,
     1, 1, PIPE_BLENDFACTOR_DST_ALPHA, PIPE_BLENDFACTOR_INV_SRC_ALPHA},
   { PictOpAtopReverse,
     1, 1, PIPE_BLENDFACTOR_INV_DST_ALPHA, PIPE_BLENDFACTOR_SRC_ALPHA},
   { PictOpXor,
     1, 1, PIPE_BLENDFACTOR_INV_DST_ALPHA, PIPE_BLENDFACTOR_INV_SRC_ALPHA},
   { PictOpAdd,
     0, 0, PIPE_BLENDFACTOR_ONE, PIPE_BLENDFACTOR_ONE},
};


static INLINE void
pixel_to_float4(Pixel pixel, float *color)
{
   CARD32	    r, g, b, a;

   a = (pixel >> 24) & 0xff;
   r = (pixel >> 16) & 0xff;
   g = (pixel >>  8) & 0xff;
   b = (pixel >>  0) & 0xff;
   color[0] = ((float)r) / 255.;
   color[1] = ((float)g) / 255.;
   color[2] = ((float)b) / 255.;
   color[3] = ((float)a) / 255.;
}

static boolean
blend_for_op(struct xorg_composite_blend *blend,
             int op, PicturePtr pSrcPicture, PicturePtr pMaskPicture,
             PicturePtr pDstPicture)
{
   const int num_blends =
      sizeof(xorg_blends)/sizeof(struct xorg_composite_blend);
   int i;
   boolean supported = FALSE;

   /* our default in case something goes wrong */
   *blend = xorg_blends[BLEND_OP_OVER];

   for (i = 0; i < num_blends; ++i) {
      if (xorg_blends[i].op == op) {
         *blend = xorg_blends[i];
         supported = TRUE;
      }
   }

   /* If there's no dst alpha channel, adjust the blend op so that we'll treat
    * it as always 1. */
   if (pDstPicture &&
       PICT_FORMAT_A(pDstPicture->format) == 0 && blend->alpha_dst) {
      if (blend->rgb_src == PIPE_BLENDFACTOR_DST_ALPHA)
         blend->rgb_src = PIPE_BLENDFACTOR_ONE;
      else if (blend->rgb_src == PIPE_BLENDFACTOR_INV_DST_ALPHA)
         blend->rgb_src = PIPE_BLENDFACTOR_ZERO;
   }

   /* If the source alpha is being used, then we should only be in a case where
    * the source blend factor is 0, and the source blend value is the mask
    * channels multiplied by the source picture's alpha. */
   if (pMaskPicture && pMaskPicture->componentAlpha &&
       PICT_FORMAT_RGB(pMaskPicture->format) && blend->alpha_src) {
      if (blend->rgb_dst == PIPE_BLENDFACTOR_SRC_ALPHA) {
         blend->rgb_dst = PIPE_BLENDFACTOR_SRC_COLOR;
      } else if (blend->rgb_dst == PIPE_BLENDFACTOR_INV_SRC_ALPHA) {
         blend->rgb_dst = PIPE_BLENDFACTOR_INV_SRC_COLOR;
      }
   }

   return supported;
}

static INLINE int
render_repeat_to_gallium(int mode)
{
   switch(mode) {
   case RepeatNone:
      return PIPE_TEX_WRAP_CLAMP_TO_BORDER;
   case RepeatNormal:
      return PIPE_TEX_WRAP_REPEAT;
   case RepeatReflect:
      return PIPE_TEX_WRAP_MIRROR_REPEAT;
   case RepeatPad:
      return PIPE_TEX_WRAP_CLAMP_TO_EDGE;
   default:
      debug_printf("Unsupported repeat mode\n");
   }
   return PIPE_TEX_WRAP_REPEAT;
}

static INLINE boolean
render_filter_to_gallium(int xrender_filter, int *out_filter)
{

   switch (xrender_filter) {
   case PictFilterNearest:
      *out_filter = PIPE_TEX_FILTER_NEAREST;
      break;
   case PictFilterBilinear:
      *out_filter = PIPE_TEX_FILTER_LINEAR;
      break;
   case PictFilterFast:
      *out_filter = PIPE_TEX_FILTER_NEAREST;
      break;
   case PictFilterGood:
      *out_filter = PIPE_TEX_FILTER_LINEAR;
      break;
   case PictFilterBest:
      *out_filter = PIPE_TEX_FILTER_LINEAR;
      break;
   case PictFilterConvolution:
      *out_filter = PIPE_TEX_FILTER_NEAREST;
      return FALSE;
   default:
      debug_printf("Unknown xrender filter\n");
      *out_filter = PIPE_TEX_FILTER_NEAREST;
      return FALSE;
   }

   return TRUE;
}

static boolean is_filter_accelerated(PicturePtr pic)
{
   int filter;
   if (pic && !render_filter_to_gallium(pic->filter, &filter))
       return FALSE;
   return TRUE;
}

boolean xorg_composite_accelerated(int op,
                                   PicturePtr pSrcPicture,
                                   PicturePtr pMaskPicture,
                                   PicturePtr pDstPicture)
{
   ScreenPtr pScreen = pDstPicture->pDrawable->pScreen;
   ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];
   modesettingPtr ms = modesettingPTR(pScrn);
   struct xorg_composite_blend blend;

   if (!is_filter_accelerated(pSrcPicture) ||
       !is_filter_accelerated(pMaskPicture)) {
      XORG_FALLBACK("Unsupported Xrender filter");
   }

   if (pSrcPicture->pSourcePict) {
      if (pSrcPicture->pSourcePict->type != SourcePictTypeSolidFill)
         XORG_FALLBACK("Gradients not enabled (haven't been well tested)");
   }

   if (blend_for_op(&blend, op,
                    pSrcPicture, pMaskPicture, pDstPicture)) {
      /* Check for component alpha */
      if (pMaskPicture && pMaskPicture->componentAlpha &&
          PICT_FORMAT_RGB(pMaskPicture->format)) {
         if (blend.alpha_src && blend.rgb_src != PIPE_BLENDFACTOR_ZERO) {
            XORG_FALLBACK("Component alpha not supported with source "
                          "alpha and source value blending. (op=%d)",
                          op);
         }
      }

      return TRUE;
   }
   XORG_FALLBACK("Unsupported composition operation = %d", op);
}

static void
bind_blend_state(struct exa_context *exa, int op,
                 PicturePtr pSrcPicture,
                 PicturePtr pMaskPicture,
                 PicturePtr pDstPicture)
{
   struct xorg_composite_blend blend_opt;
   struct pipe_blend_state blend;

   blend_for_op(&blend_opt, op, pSrcPicture, pMaskPicture, pDstPicture);

   memset(&blend, 0, sizeof(struct pipe_blend_state));
   blend.rt[0].blend_enable = 1;
   blend.rt[0].colormask = PIPE_MASK_RGBA;

   blend.rt[0].rgb_src_factor   = blend_opt.rgb_src;
   blend.rt[0].alpha_src_factor = blend_opt.rgb_src;
   blend.rt[0].rgb_dst_factor   = blend_opt.rgb_dst;
   blend.rt[0].alpha_dst_factor = blend_opt.rgb_dst;

   cso_set_blend(exa->renderer->cso, &blend);
}

static unsigned
picture_format_fixups(struct exa_pixmap_priv *pSrc, PicturePtr pSrcPicture, boolean mask,
                      PicturePtr pDstPicture)
{
   boolean set_alpha = FALSE;
   boolean swizzle = FALSE;
   unsigned ret = 0;

   if (pSrc && pSrc->picture_format == pSrcPicture->format) {
      if (pSrc->picture_format == PICT_a8) {
         if (mask)
            return FS_MASK_LUMINANCE;
         else if (pDstPicture->format != PICT_a8) {
            /* if both dst and src are luminance then
             * we don't want to swizzle the alpha (X) of the
             * source into W component of the dst because
             * it will break our destination */
            return FS_SRC_LUMINANCE;
         }
      }
      return 0;
   }

   if (pSrc && pSrc->picture_format != PICT_a8r8g8b8) {
      assert(!"can not handle formats");
      return 0;
   }

   /* pSrc->picture_format == PICT_a8r8g8b8 */
   switch (pSrcPicture->format) {
   case PICT_x8b8g8r8:
   case PICT_b8g8r8:
      set_alpha = TRUE; /* fall trough */
   case PICT_a8b8g8r8:
      swizzle = TRUE;
      break;
   case PICT_x8r8g8b8:
   case PICT_r8g8b8:
      set_alpha = TRUE; /* fall through */
   case PICT_a8r8g8b8:
      break;
#ifdef PICT_TYPE_BGRA
   case PICT_b8g8r8a8:
   case PICT_b8g8r8x8:
   case PICT_a2r10g10b10:
   case PICT_x2r10g10b10:
   case PICT_a2b10g10r10:
   case PICT_x2b10g10r10:
#endif
   default:
      assert(!"can not handle formats");
      return 0;
   }

   if (set_alpha)
      ret |= mask ? FS_MASK_SET_ALPHA : FS_SRC_SET_ALPHA;
   if (swizzle)
      ret |= mask ? FS_MASK_SWIZZLE_RGB : FS_SRC_SWIZZLE_RGB;

   return ret;
}

static void
bind_shaders(struct exa_context *exa, int op,
             PicturePtr pSrcPicture, PicturePtr pMaskPicture, PicturePtr pDstPicture,
             struct exa_pixmap_priv *pSrc, struct exa_pixmap_priv *pMask)
{
   unsigned vs_traits = 0, fs_traits = 0;
   struct xorg_shader shader;

   exa->has_solid_color = FALSE;

   if (pSrcPicture) {
      if (pSrcPicture->repeatType == RepeatNone && pSrcPicture->transform)
         fs_traits |= FS_SRC_REPEAT_NONE;

      if (pSrcPicture->pSourcePict) {
         if (pSrcPicture->pSourcePict->type == SourcePictTypeSolidFill) {
            fs_traits |= FS_SOLID_FILL;
            vs_traits |= VS_SOLID_FILL;
            debug_assert(pSrcPicture->format == PICT_a8r8g8b8);
            pixel_to_float4(pSrcPicture->pSourcePict->solidFill.color,
                            exa->solid_color);
            exa->has_solid_color = TRUE;
         } else {
            debug_assert("!gradients not supported");
         }
      } else {
         fs_traits |= FS_COMPOSITE;
         vs_traits |= VS_COMPOSITE;
      }

      fs_traits |= picture_format_fixups(pSrc, pSrcPicture, FALSE, pDstPicture);
   }

   if (pMaskPicture) {
      vs_traits |= VS_MASK;
      fs_traits |= FS_MASK;
      if (pMaskPicture->repeatType == RepeatNone && pMaskPicture->transform)
         fs_traits |= FS_MASK_REPEAT_NONE;
      if (pMaskPicture->componentAlpha) {
         struct xorg_composite_blend blend;
         blend_for_op(&blend, op,
                      pSrcPicture, pMaskPicture, NULL);
         if (blend.alpha_src) {
            fs_traits |= FS_CA_SRCALPHA;
         } else
            fs_traits |= FS_CA_FULL;
      }

      fs_traits |= picture_format_fixups(pMask, pMaskPicture, TRUE, pDstPicture);
   }

   shader = xorg_shaders_get(exa->renderer->shaders, vs_traits, fs_traits);
   cso_set_vertex_shader_handle(exa->renderer->cso, shader.vs);
   cso_set_fragment_shader_handle(exa->renderer->cso, shader.fs);
}

static void
bind_samplers(struct exa_context *exa, int op,
              PicturePtr pSrcPicture, PicturePtr pMaskPicture,
              PicturePtr pDstPicture,
              struct exa_pixmap_priv *pSrc,
              struct exa_pixmap_priv *pMask,
              struct exa_pixmap_priv *pDst)
{
   struct pipe_sampler_state *samplers[PIPE_MAX_SAMPLERS] = {0};
   struct pipe_sampler_state src_sampler, mask_sampler;
   struct pipe_sampler_view view_templ;
   struct pipe_sampler_view *src_view;
   struct pipe_context *pipe = exa->pipe;

   exa->num_bound_samplers = 0;

   memset(&src_sampler, 0, sizeof(struct pipe_sampler_state));
   memset(&mask_sampler, 0, sizeof(struct pipe_sampler_state));

   if (pSrcPicture && pSrc) {
      if (exa->has_solid_color) {
         debug_assert(!"solid color with textures");
         samplers[0] = NULL;
         pipe_sampler_view_reference(&exa->bound_sampler_views[0], NULL);
      } else {
         unsigned src_wrap = render_repeat_to_gallium(
            pSrcPicture->repeatType);
         int filter;

         render_filter_to_gallium(pSrcPicture->filter, &filter);

         src_sampler.wrap_s = src_wrap;
         src_sampler.wrap_t = src_wrap;
         src_sampler.min_img_filter = filter;
         src_sampler.mag_img_filter = filter;
         src_sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NEAREST;
         src_sampler.normalized_coords = 1;
         samplers[0] = &src_sampler;
         exa->num_bound_samplers = 1;
         u_sampler_view_default_template(&view_templ,
                                         pSrc->tex,
                                         pSrc->tex->format);
         src_view = pipe->create_sampler_view(pipe, pSrc->tex, &view_templ);
         pipe_sampler_view_reference(&exa->bound_sampler_views[0], NULL);
         exa->bound_sampler_views[0] = src_view;
      }
   }

   if (pMaskPicture && pMask) {
      unsigned mask_wrap = render_repeat_to_gallium(
         pMaskPicture->repeatType);
      int filter;

      render_filter_to_gallium(pMaskPicture->filter, &filter);

      mask_sampler.wrap_s = mask_wrap;
      mask_sampler.wrap_t = mask_wrap;
      mask_sampler.min_img_filter = filter;
      mask_sampler.mag_img_filter = filter;
      src_sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NEAREST;
      mask_sampler.normalized_coords = 1;
      samplers[1] = &mask_sampler;
      exa->num_bound_samplers = 2;
      u_sampler_view_default_template(&view_templ,
                                      pMask->tex,
                                      pMask->tex->format);
      src_view = pipe->create_sampler_view(pipe, pMask->tex, &view_templ);
      pipe_sampler_view_reference(&exa->bound_sampler_views[1], NULL);
      exa->bound_sampler_views[1] = src_view;
   }

   cso_set_samplers(exa->renderer->cso, exa->num_bound_samplers,
                    (const struct pipe_sampler_state **)samplers);
   cso_set_fragment_sampler_views(exa->renderer->cso, exa->num_bound_samplers,
                                  exa->bound_sampler_views);
}



static INLINE boolean matrix_from_pict_transform(PictTransform *trans, float *matrix)
{
   if (!trans)
      return FALSE;

   matrix[0] = XFixedToDouble(trans->matrix[0][0]);
   matrix[3] = XFixedToDouble(trans->matrix[0][1]);
   matrix[6] = XFixedToDouble(trans->matrix[0][2]);

   matrix[1] = XFixedToDouble(trans->matrix[1][0]);
   matrix[4] = XFixedToDouble(trans->matrix[1][1]);
   matrix[7] = XFixedToDouble(trans->matrix[1][2]);

   matrix[2] = XFixedToDouble(trans->matrix[2][0]);
   matrix[5] = XFixedToDouble(trans->matrix[2][1]);
   matrix[8] = XFixedToDouble(trans->matrix[2][2]);

   return TRUE;
}

static void
setup_transforms(struct  exa_context *exa,
                 PicturePtr pSrcPicture, PicturePtr pMaskPicture)
{
   PictTransform *src_t = NULL;
   PictTransform *mask_t = NULL;

   if (pSrcPicture)
      src_t = pSrcPicture->transform;
   if (pMaskPicture)
      mask_t = pMaskPicture->transform;

   exa->transform.has_src  =
      matrix_from_pict_transform(src_t, exa->transform.src);
   exa->transform.has_mask =
      matrix_from_pict_transform(mask_t, exa->transform.mask);
}

boolean xorg_composite_bind_state(struct exa_context *exa,
                                  int op,
                                  PicturePtr pSrcPicture,
                                  PicturePtr pMaskPicture,
                                  PicturePtr pDstPicture,
                                  struct exa_pixmap_priv *pSrc,
                                  struct exa_pixmap_priv *pMask,
                                  struct exa_pixmap_priv *pDst)
{
   struct pipe_surface *dst_surf = xorg_gpu_surface(exa->pipe, pDst);

   renderer_bind_destination(exa->renderer, dst_surf,
                             pDst->width,
                             pDst->height);

   bind_blend_state(exa, op, pSrcPicture, pMaskPicture, pDstPicture);
   bind_shaders(exa, op, pSrcPicture, pMaskPicture, pDstPicture, pSrc, pMask);
   bind_samplers(exa, op, pSrcPicture, pMaskPicture,
                 pDstPicture, pSrc, pMask, pDst);

   setup_transforms(exa, pSrcPicture, pMaskPicture);

   if (exa->num_bound_samplers == 0 ) { /* solid fill */
      renderer_begin_solid(exa->renderer);
   } else {
      renderer_begin_textures(exa->renderer,
                              exa->num_bound_samplers);
   }


   pipe_surface_reference(&dst_surf, NULL);
   return TRUE;
}

void xorg_composite(struct exa_context *exa,
                    struct exa_pixmap_priv *dst,
                    int srcX, int srcY, int maskX, int maskY,
                    int dstX, int dstY, int width, int height)
{
   if (exa->num_bound_samplers == 0 ) { /* solid fill */
      renderer_solid(exa->renderer,
                     dstX, dstY, dstX + width, dstY + height,
                     exa->solid_color);
   } else {
      int pos[6] = {srcX, srcY, maskX, maskY, dstX, dstY};
      float *src_matrix = NULL;
      float *mask_matrix = NULL;

      if (exa->transform.has_src)
         src_matrix = exa->transform.src;
      if (exa->transform.has_mask)
         mask_matrix = exa->transform.mask;

      renderer_texture(exa->renderer,
                       pos, width, height,
                       exa->bound_sampler_views,
                       exa->num_bound_samplers,
                       src_matrix, mask_matrix);
   }
}

boolean xorg_solid_bind_state(struct exa_context *exa,
                              struct exa_pixmap_priv *pixmap,
                              Pixel fg)
{
   struct pipe_surface *dst_surf = xorg_gpu_surface(exa->pipe, pixmap);
   unsigned vs_traits, fs_traits;
   struct xorg_shader shader;

   pixel_to_float4(fg, exa->solid_color);
   exa->has_solid_color = TRUE;

#if 0
   debug_printf("Color Pixel=(%d, %d, %d, %d), RGBA=(%f, %f, %f, %f)\n",
                (fg >> 24) & 0xff, (fg >> 16) & 0xff,
                (fg >> 8) & 0xff,  (fg >> 0) & 0xff,
                exa->solid_color[0], exa->solid_color[1],
                exa->solid_color[2], exa->solid_color[3]);
#endif

   vs_traits = VS_SOLID_FILL;
   fs_traits = FS_SOLID_FILL;

   renderer_bind_destination(exa->renderer, dst_surf, 
                             pixmap->width, pixmap->height);
   bind_blend_state(exa, PictOpSrc, NULL, NULL, NULL);
   cso_set_samplers(exa->renderer->cso, 0, NULL);
   cso_set_fragment_sampler_views(exa->renderer->cso, 0, NULL);

   shader = xorg_shaders_get(exa->renderer->shaders, vs_traits, fs_traits);
   cso_set_vertex_shader_handle(exa->renderer->cso, shader.vs);
   cso_set_fragment_shader_handle(exa->renderer->cso, shader.fs);

   renderer_begin_solid(exa->renderer);

   pipe_surface_reference(&dst_surf, NULL);
   return TRUE;
}

void xorg_solid(struct exa_context *exa,
                struct exa_pixmap_priv *pixmap,
                int x0, int y0, int x1, int y1)
{
   renderer_solid(exa->renderer,
                  x0, y0, x1, y1, exa->solid_color);
}

void
xorg_composite_done(struct exa_context *exa)
{
   renderer_draw_flush(exa->renderer);

   exa->transform.has_src = FALSE;
   exa->transform.has_mask = FALSE;
   exa->has_solid_color = FALSE;
   exa->num_bound_samplers = 0;
}
