/**************************************************************************
 *
 * Copyright 2009 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 VMWARE 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.
 *
 **************************************************************************/

#include "VG/openvg.h"

#include "vg_context.h"
#include "image.h"
#include "renderer.h"
#include "shaders_cache.h"
#include "st_inlines.h"

#include "pipe/p_context.h"
#include "pipe/p_state.h"
#include "pipe/p_inlines.h"
#include "pipe/p_screen.h"
#include "pipe/p_shader_tokens.h"

#include "util/u_memory.h"


#include "asm_filters.h"


struct filter_info {
   struct vg_image *dst;
   struct vg_image *src;
   struct vg_shader * (*setup_shader)(struct vg_context *, void *);
   void *user_data;
   const void *const_buffer;
   VGint const_buffer_len;
   VGTilingMode tiling_mode;
   struct pipe_texture *extra_texture;
};

static INLINE struct pipe_texture *create_texture_1d(struct vg_context *ctx,
                                                     const VGuint *color_data,
                                                     const VGint color_data_len)
{
   struct pipe_context *pipe = ctx->pipe;
   struct pipe_screen *screen = pipe->screen;
   struct pipe_texture *tex = 0;
   struct pipe_texture templ;

   memset(&templ, 0, sizeof(templ));
   templ.target = PIPE_TEXTURE_1D;
   templ.format = PIPE_FORMAT_A8R8G8B8_UNORM;
   templ.last_level = 0;
   templ.width[0] = color_data_len;
   templ.height[0] = 1;
   templ.depth[0] = 1;
   pf_get_block(PIPE_FORMAT_A8R8G8B8_UNORM, &templ.block);
   templ.tex_usage = PIPE_TEXTURE_USAGE_SAMPLER;

   tex = screen->texture_create(screen, &templ);

   { /* upload color_data */
      struct pipe_transfer *transfer =
         screen->get_tex_transfer(screen, tex,
                                  0, 0, 0,
                                  PIPE_TRANSFER_READ_WRITE ,
                                  0, 0, tex->width[0], tex->height[0]);
      void *map = screen->transfer_map(screen, transfer);
      memcpy(map, color_data, sizeof(VGint)*color_data_len);
      screen->transfer_unmap(screen, transfer);
      screen->tex_transfer_destroy(transfer);
   }

   return tex;
}

static INLINE struct pipe_surface * setup_framebuffer(struct vg_image *dst)
{
   struct vg_context *ctx = vg_current_context();
   struct pipe_context *pipe = ctx->pipe;
   struct pipe_framebuffer_state fb;
   struct pipe_surface *dst_surf = pipe->screen->get_tex_surface(
      pipe->screen, dst->texture, 0, 0, 0,
      PIPE_BUFFER_USAGE_GPU_WRITE);

   /* drawing dest */
   memset(&fb, 0, sizeof(fb));
   fb.width  = dst->x + dst_surf->width;
   fb.height = dst->y + dst_surf->height;
   fb.nr_cbufs = 1;
   fb.cbufs[0] = dst_surf;
   {
      VGint i;
      for (i = 1; i < PIPE_MAX_COLOR_BUFS; ++i)
         fb.cbufs[i] = 0;
   }
   cso_set_framebuffer(ctx->cso_context, &fb);

   return dst_surf;
}

static void setup_viewport(struct vg_image *dst)
{
   struct vg_context *ctx = vg_current_context();
   vg_set_viewport(ctx, VEGA_Y0_TOP);
}

static void setup_blend()
{
   struct vg_context *ctx = vg_current_context();
   struct pipe_blend_state blend;
   memset(&blend, 0, sizeof(blend));
   blend.rgb_src_factor = PIPE_BLENDFACTOR_ONE;
   blend.alpha_src_factor = PIPE_BLENDFACTOR_ONE;
   blend.rgb_dst_factor = PIPE_BLENDFACTOR_ZERO;
   blend.alpha_dst_factor = PIPE_BLENDFACTOR_ZERO;
   if (ctx->state.vg.filter_channel_mask & VG_RED)
      blend.colormask |= PIPE_MASK_R;
   if (ctx->state.vg.filter_channel_mask & VG_GREEN)
      blend.colormask |= PIPE_MASK_G;
   if (ctx->state.vg.filter_channel_mask & VG_BLUE)
      blend.colormask |= PIPE_MASK_B;
   if (ctx->state.vg.filter_channel_mask & VG_ALPHA)
      blend.colormask |= PIPE_MASK_A;
   blend.blend_enable = 1;
   cso_set_blend(ctx->cso_context, &blend);
}

static void setup_constant_buffer(struct vg_context *ctx, const void *buffer,
                                  VGint param_bytes)
{
   struct pipe_context *pipe = ctx->pipe;
   struct pipe_constant_buffer *cbuf = &ctx->filter.buffer;

   /* We always need to get a new buffer, to keep the drivers simple and
    * avoid gratuitous rendering synchronization. */
   pipe_buffer_reference(&cbuf->buffer, NULL);

   cbuf->buffer = pipe_buffer_create(pipe->screen, 16,
                                     PIPE_BUFFER_USAGE_CONSTANT,
                                     param_bytes);

   if (cbuf->buffer) {
      st_no_flush_pipe_buffer_write(ctx, cbuf->buffer,
                                    0, param_bytes, buffer);
   }

   ctx->pipe->set_constant_buffer(ctx->pipe, PIPE_SHADER_FRAGMENT, 0, cbuf);
}

static void setup_samplers(struct vg_context *ctx, struct filter_info *info)
{
   struct pipe_sampler_state *samplers[PIPE_MAX_SAMPLERS];
   struct pipe_texture *textures[PIPE_MAX_SAMPLERS];
   struct pipe_sampler_state sampler[3];
   int num_samplers = 0;
   int num_textures = 0;

   samplers[0] = NULL;
   samplers[1] = NULL;
   samplers[2] = NULL;
   samplers[3] = NULL;
   textures[0] = NULL;
   textures[1] = NULL;
   textures[2] = NULL;
   textures[3] = NULL;

   memset(&sampler[0], 0, sizeof(struct pipe_sampler_state));
   sampler[0].wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
   sampler[0].wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
   sampler[0].wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
   sampler[0].min_img_filter = PIPE_TEX_MIPFILTER_LINEAR;
   sampler[0].mag_img_filter = PIPE_TEX_MIPFILTER_LINEAR;
   sampler[0].normalized_coords = 1;

   switch(info->tiling_mode) {
   case VG_TILE_FILL:
      sampler[0].wrap_s = PIPE_TEX_WRAP_CLAMP_TO_BORDER;
      sampler[0].wrap_t = PIPE_TEX_WRAP_CLAMP_TO_BORDER;
      memcpy(sampler[0].border_color,
             ctx->state.vg.tile_fill_color,
             sizeof(VGfloat) * 4);
      break;
   case VG_TILE_PAD:
      sampler[0].wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
      sampler[0].wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
      break;
   case VG_TILE_REPEAT:
      sampler[0].wrap_s = PIPE_TEX_WRAP_REPEAT;
      sampler[0].wrap_t = PIPE_TEX_WRAP_REPEAT;
      break;
   case VG_TILE_REFLECT:
      sampler[0].wrap_s = PIPE_TEX_WRAP_MIRROR_REPEAT;
      sampler[0].wrap_t = PIPE_TEX_WRAP_MIRROR_REPEAT;
      break;
   default:
      debug_assert(!"Unknown tiling mode");
   }

   samplers[0] = &sampler[0];
   textures[0] = info->src->texture;
   ++num_samplers;
   ++num_textures;

   if (info->extra_texture) {
      memcpy(&sampler[1], &sampler[0], sizeof(struct pipe_sampler_state));
      samplers[1] = &sampler[1];
      textures[1] = info->extra_texture;
      ++num_samplers;
      ++num_textures;
   }


   cso_set_samplers(ctx->cso_context, num_samplers, (const struct pipe_sampler_state **)samplers);
   cso_set_sampler_textures(ctx->cso_context, num_textures, textures);
}

static struct vg_shader * setup_color_matrix(struct vg_context *ctx, void *user_data)
{
   struct vg_shader *shader =
      shader_create_from_text(ctx->pipe, color_matrix_asm, 200,
         PIPE_SHADER_FRAGMENT);
   cso_set_fragment_shader_handle(ctx->cso_context, shader->driver);
   return shader;
}

static struct vg_shader * setup_convolution(struct vg_context *ctx, void *user_data)
{
   char buffer[1024];
   VGint num_consts = (VGint)(long)(user_data);
   struct vg_shader *shader;

   snprintf(buffer, 1023, convolution_asm, num_consts, num_consts / 2 + 1);

   shader = shader_create_from_text(ctx->pipe, buffer, 200,
                                    PIPE_SHADER_FRAGMENT);

   cso_set_fragment_shader_handle(ctx->cso_context, shader->driver);
   return shader;
}

static struct vg_shader * setup_lookup(struct vg_context *ctx, void *user_data)
{
   struct vg_shader *shader =
      shader_create_from_text(ctx->pipe, lookup_asm,
                              200, PIPE_SHADER_FRAGMENT);

   cso_set_fragment_shader_handle(ctx->cso_context, shader->driver);
   return shader;
}


static struct vg_shader * setup_lookup_single(struct vg_context *ctx, void *user_data)
{
   char buffer[1024];
   VGImageChannel channel = (VGImageChannel)(user_data);
   struct vg_shader *shader;

   switch(channel) {
   case VG_RED:
      snprintf(buffer, 1023, lookup_single_asm, "xxxx");
      break;
   case VG_GREEN:
      snprintf(buffer, 1023, lookup_single_asm, "yyyy");
      break;
   case VG_BLUE:
      snprintf(buffer, 1023, lookup_single_asm, "zzzz");
      break;
   case VG_ALPHA:
      snprintf(buffer, 1023, lookup_single_asm, "wwww");
      break;
   default:
      debug_assert(!"Unknown color channel");
   }

   shader = shader_create_from_text(ctx->pipe, buffer, 200,
                                    PIPE_SHADER_FRAGMENT);

   cso_set_fragment_shader_handle(ctx->cso_context, shader->driver);
   return shader;
}

static void execute_filter(struct vg_context *ctx,
                           struct filter_info *info)
{
   struct pipe_surface *dst_surf;
   struct vg_shader *shader;

   cso_save_framebuffer(ctx->cso_context);
   cso_save_fragment_shader(ctx->cso_context);
   cso_save_viewport(ctx->cso_context);
   cso_save_blend(ctx->cso_context);
   cso_save_samplers(ctx->cso_context);
   cso_save_sampler_textures(ctx->cso_context);

   dst_surf = setup_framebuffer(info->dst);
   setup_viewport(info->dst);
   setup_blend();
   setup_constant_buffer(ctx, info->const_buffer, info->const_buffer_len);
   shader = info->setup_shader(ctx, info->user_data);
   setup_samplers(ctx, info);

   renderer_draw_texture(ctx->renderer,
                         info->src->texture,
                         info->dst->x, info->dst->y,
                         info->dst->x + info->dst->width,
                         info->dst->y + info->dst->height,
                         info->dst->x, info->dst->y,
                         info->dst->x + info->dst->width,
                         info->dst->y + info->dst->height);

   cso_restore_framebuffer(ctx->cso_context);
   cso_restore_fragment_shader(ctx->cso_context);
   cso_restore_viewport(ctx->cso_context);
   cso_restore_blend(ctx->cso_context);
   cso_restore_samplers(ctx->cso_context);
   cso_restore_sampler_textures(ctx->cso_context);

   vg_shader_destroy(ctx, shader);

   pipe_surface_reference(&dst_surf, NULL);
}

void vgColorMatrix(VGImage dst, VGImage src,
                   const VGfloat * matrix)
{
   struct vg_context *ctx = vg_current_context();
   struct vg_image *d, *s;
   struct filter_info info;

   if (dst == VG_INVALID_HANDLE || src == VG_INVALID_HANDLE) {
      vg_set_error(ctx, VG_BAD_HANDLE_ERROR);
      return;
   }
   if (!matrix || !is_aligned(matrix)) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }

   d = (struct vg_image*)dst;
   s = (struct vg_image*)src;

   if (vg_image_overlaps(d, s)) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }

   info.dst = d;
   info.src = s;
   info.setup_shader = &setup_color_matrix;
   info.user_data = NULL;
   info.const_buffer = matrix;
   info.const_buffer_len = 20 * sizeof(VGfloat);
   info.tiling_mode = VG_TILE_PAD;
   info.extra_texture = 0;
   execute_filter(ctx, &info);
}

static VGfloat texture_offset(VGfloat width, VGint kernelSize, VGint current, VGint shift)
{
   VGfloat diff = current - shift;

   return diff / width;
}

void vgConvolve(VGImage dst, VGImage src,
                VGint kernelWidth, VGint kernelHeight,
                VGint shiftX, VGint shiftY,
                const VGshort * kernel,
                VGfloat scale,
                VGfloat bias,
                VGTilingMode tilingMode)
{
   struct vg_context *ctx = vg_current_context();
   VGfloat *buffer;
   VGint buffer_len;
   VGint i, j;
   VGint idx = 0;
   struct vg_image *d, *s;
   VGint kernel_size = kernelWidth * kernelHeight;
   struct filter_info info;
   const VGint max_kernel_size = vgGeti(VG_MAX_KERNEL_SIZE);

   if (dst == VG_INVALID_HANDLE || src == VG_INVALID_HANDLE) {
      vg_set_error(ctx, VG_BAD_HANDLE_ERROR);
      return;
   }

   if (kernelWidth <= 0 || kernelHeight <= 0 ||
      kernelWidth > max_kernel_size || kernelHeight > max_kernel_size) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }

   if (!kernel || !is_aligned_to(kernel, 2)) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }

   if (tilingMode < VG_TILE_FILL ||
       tilingMode > VG_TILE_REFLECT) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }

   d = (struct vg_image*)dst;
   s = (struct vg_image*)src;

   if (vg_image_overlaps(d, s)) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }

   vg_validate_state(ctx);

   buffer_len = 8 + 2 * 4 * kernel_size;
   buffer = (VGfloat*)malloc(buffer_len * sizeof(VGfloat));

   buffer[0] = 0.f;
   buffer[1] = 1.f;
   buffer[2] = 2.f; /*unused*/
   buffer[3] = 4.f; /*unused*/

   buffer[4] = kernelWidth * kernelHeight;
   buffer[5] = scale;
   buffer[6] = bias;
   buffer[7] = 0.f;

   idx = 8;
   for (j = 0; j < kernelHeight; ++j) {
      for (i = 0; i < kernelWidth; ++i) {
         VGint index = j * kernelWidth + i;
         VGfloat x, y;

         x = texture_offset(s->width, kernelWidth, i, shiftX);
         y = texture_offset(s->height, kernelHeight, j, shiftY);

         buffer[idx + index*4 + 0] = x;
         buffer[idx + index*4 + 1] = y;
         buffer[idx + index*4 + 2] = 0.f;
         buffer[idx + index*4 + 3] = 0.f;
      }
   }
   idx += kernel_size * 4;

   for (j = 0; j < kernelHeight; ++j) {
      for (i = 0; i < kernelWidth; ++i) {
         /* transpose the kernel */
         VGint index = j * kernelWidth + i;
         VGint kindex = (kernelWidth - i - 1) * kernelHeight + (kernelHeight - j - 1);
         buffer[idx + index*4 + 0] = kernel[kindex];
         buffer[idx + index*4 + 1] = kernel[kindex];
         buffer[idx + index*4 + 2] = kernel[kindex];
         buffer[idx + index*4 + 3] = kernel[kindex];
      }
   }

   info.dst = d;
   info.src = s;
   info.setup_shader = &setup_convolution;
   info.user_data = (void*)(long)(buffer_len/4);
   info.const_buffer = buffer;
   info.const_buffer_len = buffer_len * sizeof(VGfloat);
   info.tiling_mode = tilingMode;
   info.extra_texture = 0;
   execute_filter(ctx, &info);

   free(buffer);
}

void vgSeparableConvolve(VGImage dst, VGImage src,
                         VGint kernelWidth,
                         VGint kernelHeight,
                         VGint shiftX, VGint shiftY,
                         const VGshort * kernelX,
                         const VGshort * kernelY,
                         VGfloat scale,
                         VGfloat bias,
                         VGTilingMode tilingMode)
{
   struct vg_context *ctx = vg_current_context();
   VGshort *kernel;
   VGint i, j, idx = 0;
   const VGint max_kernel_size = vgGeti(VG_MAX_SEPARABLE_KERNEL_SIZE);

   if (dst == VG_INVALID_HANDLE || src == VG_INVALID_HANDLE) {
      vg_set_error(ctx, VG_BAD_HANDLE_ERROR);
      return;
   }

   if (kernelWidth <= 0 || kernelHeight <= 0 ||
       kernelWidth > max_kernel_size || kernelHeight > max_kernel_size) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }

   if (!kernelX || !kernelY ||
       !is_aligned_to(kernelX, 2) || !is_aligned_to(kernelY, 2)) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }
   if (tilingMode < VG_TILE_FILL ||
       tilingMode > VG_TILE_REFLECT) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }
   kernel = malloc(sizeof(VGshort)*kernelWidth*kernelHeight);
   for (i = 0; i < kernelWidth; ++i) {
      for (j = 0; j < kernelHeight; ++j) {
         kernel[idx] = kernelX[i] * kernelY[j];
         ++idx;
      }
   }
   vgConvolve(dst, src, kernelWidth, kernelHeight, shiftX, shiftY,
              kernel, scale, bias, tilingMode);
   free(kernel);
}

static INLINE VGfloat compute_gaussian_componenet(VGfloat x, VGfloat y,
                                                  VGfloat stdDeviationX,
                                                  VGfloat stdDeviationY)
{
   VGfloat mult = 1 / ( 2 * M_PI * stdDeviationX * stdDeviationY);
   VGfloat e = exp( - ( pow(x, 2)/(2*pow(stdDeviationX, 2)) +
                        pow(y, 2)/(2*pow(stdDeviationY, 2)) ) );
   return mult * e;
}

static INLINE VGint compute_kernel_size(VGfloat deviation)
{
   VGint size = ceil(2.146 * deviation);
   if (size > 11)
      return 11;
   return size;
}

static void compute_gaussian_kernel(VGfloat *kernel,
                                    VGint width, VGint height,
                                    VGfloat stdDeviationX,
                                    VGfloat stdDeviationY)
{
   VGint i, j;
   VGfloat scale = 0.0f;

   for (j = 0; j < height; ++j) {
      for (i = 0; i < width; ++i) {
         VGint idx =  (height - j -1) * width + (width - i -1);
         kernel[idx] = compute_gaussian_componenet(i-(ceil(width/2))-1,
                                                   j-ceil(height/2)-1,
                                                   stdDeviationX, stdDeviationY);
         scale += kernel[idx];
      }
   }

   for (j = 0; j < height; ++j) {
      for (i = 0; i < width; ++i) {
         VGint idx = j * width + i;
         kernel[idx] /= scale;
      }
   }
}

void vgGaussianBlur(VGImage dst, VGImage src,
                    VGfloat stdDeviationX,
                    VGfloat stdDeviationY,
                    VGTilingMode tilingMode)
{
   struct vg_context *ctx = vg_current_context();
   struct vg_image *d, *s;
   VGfloat *buffer, *kernel;
   VGint kernel_width, kernel_height, kernel_size;
   VGint buffer_len;
   VGint idx, i, j;
   struct filter_info info;

   if (dst == VG_INVALID_HANDLE || src == VG_INVALID_HANDLE) {
      vg_set_error(ctx, VG_BAD_HANDLE_ERROR);
      return;
   }
   if (stdDeviationX <= 0 || stdDeviationY <= 0) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }

   if (tilingMode < VG_TILE_FILL ||
       tilingMode > VG_TILE_REFLECT) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }

   d = (struct vg_image*)dst;
   s = (struct vg_image*)src;

   if (vg_image_overlaps(d, s)) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }

   kernel_width = compute_kernel_size(stdDeviationX);
   kernel_height = compute_kernel_size(stdDeviationY);
   kernel_size = kernel_width * kernel_height;
   kernel = malloc(sizeof(VGfloat)*kernel_size);
   compute_gaussian_kernel(kernel, kernel_width, kernel_height,
                           stdDeviationX, stdDeviationY);

   buffer_len = 8 + 2 * 4 * kernel_size;
   buffer = (VGfloat*)malloc(buffer_len * sizeof(VGfloat));

   buffer[0] = 0.f;
   buffer[1] = 1.f;
   buffer[2] = 2.f; /*unused*/
   buffer[3] = 4.f; /*unused*/

   buffer[4] = kernel_width * kernel_height;
   buffer[5] = 1.f;/*scale*/
   buffer[6] = 0.f;/*bias*/
   buffer[7] = 0.f;

   idx = 8;
   for (j = 0; j < kernel_height; ++j) {
      for (i = 0; i < kernel_width; ++i) {
         VGint index = j * kernel_width + i;
         VGfloat x, y;

         x = texture_offset(s->width, kernel_width, i, kernel_width/2);
         y = texture_offset(s->height, kernel_height, j, kernel_height/2);

         buffer[idx + index*4 + 0] = x;
         buffer[idx + index*4 + 1] = y;
         buffer[idx + index*4 + 2] = 0.f;
         buffer[idx + index*4 + 3] = 0.f;
      }
   }
   idx += kernel_size * 4;

   for (j = 0; j < kernel_height; ++j) {
      for (i = 0; i < kernel_width; ++i) {
         /* transpose the kernel */
         VGint index = j * kernel_width + i;
         VGint kindex = (kernel_width - i - 1) * kernel_height + (kernel_height - j - 1);
         buffer[idx + index*4 + 0] = kernel[kindex];
         buffer[idx + index*4 + 1] = kernel[kindex];
         buffer[idx + index*4 + 2] = kernel[kindex];
         buffer[idx + index*4 + 3] = kernel[kindex];
      }
   }

   info.dst = d;
   info.src = s;
   info.setup_shader = &setup_convolution;
   info.user_data = (void*)(long)(buffer_len/4);
   info.const_buffer = buffer;
   info.const_buffer_len = buffer_len * sizeof(VGfloat);
   info.tiling_mode = tilingMode;
   info.extra_texture = 0;
   execute_filter(ctx, &info);

   free(buffer);
   free(kernel);
}

void vgLookup(VGImage dst, VGImage src,
              const VGubyte * redLUT,
              const VGubyte * greenLUT,
              const VGubyte * blueLUT,
              const VGubyte * alphaLUT,
              VGboolean outputLinear,
              VGboolean outputPremultiplied)
{
   struct vg_context *ctx = vg_current_context();
   struct vg_image *d, *s;
   VGuint color_data[256];
   VGint i;
   struct pipe_texture *lut_texture;
   VGfloat buffer[4];
   struct filter_info info;

   if (dst == VG_INVALID_HANDLE || src == VG_INVALID_HANDLE) {
      vg_set_error(ctx, VG_BAD_HANDLE_ERROR);
      return;
   }

   if (!redLUT || !greenLUT || !blueLUT || !alphaLUT) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }

   d = (struct vg_image*)dst;
   s = (struct vg_image*)src;

   if (vg_image_overlaps(d, s)) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }

   for (i = 0; i < 256; ++i) {
      color_data[i] = blueLUT[i] << 24 | greenLUT[i] << 16 |
                      redLUT[i]  <<  8 | alphaLUT[i];
   }
   lut_texture = create_texture_1d(ctx, color_data, 255);

   buffer[0] = 0.f;
   buffer[1] = 0.f;
   buffer[2] = 1.f;
   buffer[3] = 1.f;

   info.dst = d;
   info.src = s;
   info.setup_shader = &setup_lookup;
   info.user_data = NULL;
   info.const_buffer = buffer;
   info.const_buffer_len = 4 * sizeof(VGfloat);
   info.tiling_mode = VG_TILE_PAD;
   info.extra_texture = lut_texture;

   execute_filter(ctx, &info);

   pipe_texture_reference(&lut_texture, NULL);
}

void vgLookupSingle(VGImage dst, VGImage src,
                    const VGuint * lookupTable,
                    VGImageChannel sourceChannel,
                    VGboolean outputLinear,
                    VGboolean outputPremultiplied)
{
   struct vg_context *ctx = vg_current_context();
   struct vg_image *d, *s;
   struct pipe_texture *lut_texture;
   VGfloat buffer[4];
   struct filter_info info;
   VGuint color_data[256];
   VGint i;

   if (dst == VG_INVALID_HANDLE || src == VG_INVALID_HANDLE) {
      vg_set_error(ctx, VG_BAD_HANDLE_ERROR);
      return;
   }

   if (!lookupTable || !is_aligned(lookupTable)) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }

   if (sourceChannel != VG_RED && sourceChannel != VG_GREEN &&
       sourceChannel != VG_BLUE && sourceChannel != VG_ALPHA) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }

   d = (struct vg_image*)dst;
   s = (struct vg_image*)src;

   if (vg_image_overlaps(d, s)) {
      vg_set_error(ctx, VG_ILLEGAL_ARGUMENT_ERROR);
      return;
   }

   for (i = 0; i < 256; ++i) {
      VGuint rgba = lookupTable[i];
      VGubyte blue, green, red, alpha;
      red   = (rgba & 0xff000000)>>24;
      green = (rgba & 0x00ff0000)>>16;
      blue  = (rgba & 0x0000ff00)>> 8;
      alpha = (rgba & 0x000000ff)>> 0;
      color_data[i] = blue << 24 | green << 16 |
                      red  <<  8 | alpha;
   }
   lut_texture = create_texture_1d(ctx, color_data, 256);

   buffer[0] = 0.f;
   buffer[1] = 0.f;
   buffer[2] = 1.f;
   buffer[3] = 1.f;

   info.dst = d;
   info.src = s;
   info.setup_shader = &setup_lookup_single;
   info.user_data = (void*)sourceChannel;
   info.const_buffer = buffer;
   info.const_buffer_len = 4 * sizeof(VGfloat);
   info.tiling_mode = VG_TILE_PAD;
   info.extra_texture = lut_texture;

   execute_filter(ctx, &info);

   pipe_texture_reference(&lut_texture, NULL);
}
