/*
 * Cogl
 *
 * A Low Level GPU Graphics and Utilities API
 *
 * Copyright (C) 2011 Collabora Ltd.
 * Copyright (C) 2012 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 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:
 *  Tomeu Vizoso <tomeu.vizoso@collabora.com>
 *  Robert Bragg <robert@linux.intel.com>
 *  Neil Roberts <neil@linux.intel.com>
 *
 */

#ifdef HAVE_CONFIG_H
#include "cogl-config.h"
#endif

#include <string.h>

#include "cogl-gles2.h"
#include "cogl-gles2-context-private.h"

#include "cogl-context-private.h"
#include "cogl-display-private.h"
#include "cogl-framebuffer-private.h"
#include "cogl-framebuffer-gl-private.h"
#include "cogl-onscreen-template-private.h"
#include "cogl-renderer-private.h"
#include "cogl-swap-chain-private.h"
#include "cogl-texture-2d-gl.h"
#include "cogl-texture-2d-private.h"
#include "cogl-pipeline-opengl-private.h"
#include "cogl-error-private.h"
#include "cogl-gtype-private.h"

static void _cogl_gles2_context_free (CoglGLES2Context *gles2_context);

COGL_OBJECT_DEFINE (GLES2Context, gles2_context);
COGL_GTYPE_DEFINE_CLASS (GLES2Context, gles2_context);

static CoglGLES2Context *current_gles2_context;

static CoglUserDataKey offscreen_wrapper_key;

/* The application's main function is renamed to this so that we can
 * provide an alternative main function */
#define MAIN_WRAPPER_REPLACEMENT_NAME "_c31"
/* This uniform is used to flip the rendering or not depending on
 * whether we are rendering to an offscreen buffer or not */
#define MAIN_WRAPPER_FLIP_UNIFORM "_cogl_flip_vector"
/* These comments are used to delimit the added wrapper snippet so
 * that we can remove it again when the shader source is requested via
 * glGetShaderSource */
#define MAIN_WRAPPER_BEGIN "/*_COGL_WRAPPER_BEGIN*/"
#define MAIN_WRAPPER_END "/*_COGL_WRAPPER_END*/"

/* This wrapper function around 'main' is appended to every vertex shader
 * so that we can add some extra code to flip the rendering when
 * rendering to an offscreen buffer */
static const char
main_wrapper_function[] =
  MAIN_WRAPPER_BEGIN "\n"
  "uniform vec4 " MAIN_WRAPPER_FLIP_UNIFORM ";\n"
  "\n"
  "void\n"
  "main ()\n"
  "{\n"
  "  " MAIN_WRAPPER_REPLACEMENT_NAME " ();\n"
  "  gl_Position *= " MAIN_WRAPPER_FLIP_UNIFORM ";\n"
  "}\n"
  MAIN_WRAPPER_END;

enum {
  RESTORE_FB_NONE,
  RESTORE_FB_FROM_OFFSCREEN,
  RESTORE_FB_FROM_ONSCREEN,
};

uint32_t
_cogl_gles2_context_error_quark (void)
{
  return g_quark_from_static_string ("cogl-gles2-context-error-quark");
}

static void
shader_data_unref (CoglGLES2Context *context,
                   CoglGLES2ShaderData *shader_data)
{
  if (--shader_data->ref_count < 1)
    /* Removing the hash table entry should also destroy the data */
    g_hash_table_remove (context->shader_map,
                         GINT_TO_POINTER (shader_data->object_id));
}

static void
program_data_unref (CoglGLES2ProgramData *program_data)
{
  if (--program_data->ref_count < 1)
    /* Removing the hash table entry should also destroy the data */
    g_hash_table_remove (program_data->context->program_map,
                         GINT_TO_POINTER (program_data->object_id));
}

static void
detach_shader (CoglGLES2ProgramData *program_data,
               CoglGLES2ShaderData *shader_data)
{
  GList *l;

  for (l = program_data->attached_shaders; l; l = l->next)
    {
      if (l->data == shader_data)
        {
          shader_data_unref (program_data->context, shader_data);
          program_data->attached_shaders =
            g_list_delete_link (program_data->attached_shaders, l);
          break;
        }
    }
}

static CoglBool
is_symbol_character (char ch)
{
  return g_ascii_isalnum (ch) || ch == '_';
}

static void
replace_token (char *string,
               const char *token,
               const char *replacement,
               int length)
{
  char *token_pos;
  char *last_pos = string;
  char *end = string + length;
  int token_length = strlen (token);

  /* NOTE: this assumes token and replacement are the same length */

  while ((token_pos = _cogl_util_memmem (last_pos,
                                         end - last_pos,
                                         token,
                                         token_length)))
    {
      /* Make sure this isn't in the middle of some longer token */
      if ((token_pos <= string ||
           !is_symbol_character (token_pos[-1])) &&
          (token_pos + token_length == end ||
           !is_symbol_character (token_pos[token_length])))
        memcpy (token_pos, replacement, token_length);

      last_pos = token_pos + token_length;
    }
}

static void
update_current_flip_state (CoglGLES2Context *gles2_ctx)
{
  CoglGLES2FlipState new_flip_state;

  if (gles2_ctx->current_fbo_handle == 0 &&
      cogl_is_offscreen (gles2_ctx->write_buffer))
    new_flip_state = COGL_GLES2_FLIP_STATE_FLIPPED;
  else
    new_flip_state = COGL_GLES2_FLIP_STATE_NORMAL;

  /* If the flip state has changed then we need to reflush all of the
   * dependent state */
  if (new_flip_state != gles2_ctx->current_flip_state)
    {
      gles2_ctx->viewport_dirty = TRUE;
      gles2_ctx->scissor_dirty = TRUE;
      gles2_ctx->front_face_dirty = TRUE;
      gles2_ctx->current_flip_state = new_flip_state;
    }
}

static GLuint
get_current_texture_2d_object (CoglGLES2Context *gles2_ctx)
{
  return g_array_index (gles2_ctx->texture_units,
                        CoglGLES2TextureUnitData,
                        gles2_ctx->current_texture_unit).current_texture_2d;
}

static void
set_texture_object_data (CoglGLES2Context *gles2_ctx,
                         GLenum target,
                         GLint level,
                         GLenum internal_format,
                         GLsizei width,
                         GLsizei height)
{
  GLuint texture_id = get_current_texture_2d_object (gles2_ctx);
  CoglGLES2TextureObjectData *texture_object;

  /* We want to keep track of all texture objects where the data is
   * created by this context so that we can delete them later */
  texture_object = g_hash_table_lookup (gles2_ctx->texture_object_map,
                                        GUINT_TO_POINTER (texture_id));
  if (texture_object == NULL)
    {
      texture_object = g_slice_new0 (CoglGLES2TextureObjectData);
      texture_object->object_id = texture_id;

      g_hash_table_insert (gles2_ctx->texture_object_map,
                           GUINT_TO_POINTER (texture_id),
                           texture_object);
    }

  switch (target)
    {
    case GL_TEXTURE_2D:
      texture_object->target = GL_TEXTURE_2D;

      /* We want to keep track of the dimensions of any texture object
       * setting the GL_TEXTURE_2D target */
      if (level == 0)
        {
          texture_object->width = width;
          texture_object->height = height;
          texture_object->format = internal_format;
        }
      break;

    case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
    case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
    case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
    case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
      texture_object->target = GL_TEXTURE_CUBE_MAP;
      break;
    }
}

static void
copy_flipped_texture (CoglGLES2Context *gles2_ctx,
                      int level,
                      int src_x,
                      int src_y,
                      int dst_x,
                      int dst_y,
                      int width,
                      int height)
{
  GLuint tex_id = get_current_texture_2d_object (gles2_ctx);
  CoglGLES2TextureObjectData *tex_object_data;
  CoglContext *ctx;
  const CoglWinsysVtable *winsys;
  CoglTexture2D *dst_texture;
  CoglPixelFormat internal_format;

  tex_object_data = g_hash_table_lookup (gles2_ctx->texture_object_map,
                                         GUINT_TO_POINTER (tex_id));

  /* We can't do anything if the application hasn't set a level 0
   * image on this texture object */
  if (tex_object_data == NULL ||
      tex_object_data->target != GL_TEXTURE_2D ||
      tex_object_data->width <= 0 ||
      tex_object_data->height <= 0)
    return;

  switch (tex_object_data->format)
    {
    case GL_RGB:
      internal_format = COGL_PIXEL_FORMAT_RGB_888;
      break;

    case GL_RGBA:
      internal_format = COGL_PIXEL_FORMAT_RGBA_8888_PRE;
      break;

    case GL_ALPHA:
      internal_format = COGL_PIXEL_FORMAT_A_8;
      break;

    case GL_LUMINANCE:
      internal_format = COGL_PIXEL_FORMAT_G_8;
      break;

    default:
      /* We can't handle this format so just give up */
      return;
    }

  ctx = gles2_ctx->context;
  winsys = ctx->display->renderer->winsys_vtable;

  /* We need to make sure the rendering on the GLES2 context is
   * complete before the blit will be ready in the GLES2 context */
  ctx->glFinish ();
  /* We need to force Cogl to rebind the texture because according to
   * the GL spec a shared texture isn't guaranteed to be updated until
   * is rebound */
  _cogl_get_texture_unit (0)->dirty_gl_texture = TRUE;

  /* Temporarily switch back to the Cogl context */
  winsys->restore_context (ctx);

  dst_texture =
    cogl_gles2_texture_2d_new_from_handle (gles2_ctx->context,
                                           gles2_ctx,
                                           tex_id,
                                           tex_object_data->width,
                                           tex_object_data->height,
                                           internal_format);

  if (dst_texture)
    {
      CoglTexture *src_texture =
        COGL_OFFSCREEN (gles2_ctx->read_buffer)->texture;
      CoglPipeline *pipeline = cogl_pipeline_new (ctx);
      const CoglOffscreenFlags flags = COGL_OFFSCREEN_DISABLE_DEPTH_AND_STENCIL;
      CoglOffscreen *offscreen =
        _cogl_offscreen_new_with_texture_full (COGL_TEXTURE (dst_texture),
                                             flags, level);
      int src_width = cogl_texture_get_width (src_texture);
      int src_height = cogl_texture_get_height (src_texture);
      /* The framebuffer size might be different from the texture size
       * if a level > 0 is used */
      int dst_width =
        cogl_framebuffer_get_width (COGL_FRAMEBUFFER (offscreen));
      int dst_height =
        cogl_framebuffer_get_height (COGL_FRAMEBUFFER (offscreen));
      float x_1, y_1, x_2, y_2, s_1, t_1, s_2, t_2;

      cogl_pipeline_set_layer_texture (pipeline, 0, src_texture);
      cogl_pipeline_set_blend (pipeline,
                               "RGBA = ADD(SRC_COLOR, 0)",
                               NULL);
      cogl_pipeline_set_layer_filters (pipeline,
                                       0, /* layer_num */
                                       COGL_PIPELINE_FILTER_NEAREST,
                                       COGL_PIPELINE_FILTER_NEAREST);

      x_1 = dst_x * 2.0f / dst_width - 1.0f;
      y_1 = dst_y * 2.0f / dst_height - 1.0f;
      x_2 = x_1 + width * 2.0f / dst_width;
      y_2 = y_1 + height * 2.0f / dst_height;

      s_1 = src_x / (float) src_width;
      t_1 = 1.0f - src_y / (float) src_height;
      s_2 = (src_x + width) / (float) src_width;
      t_2 = 1.0f - (src_y + height) / (float) src_height;

      cogl_framebuffer_draw_textured_rectangle (COGL_FRAMEBUFFER (offscreen),
                                                pipeline,
                                                x_1, y_1,
                                                x_2, y_2,
                                                s_1, t_1,
                                                s_2, t_2);

      _cogl_framebuffer_flush_journal (COGL_FRAMEBUFFER (offscreen));

      /* We need to make sure the rendering is complete before the
       * blit will be ready in the GLES2 context */
      ctx->glFinish ();

      cogl_object_unref (pipeline);
      cogl_object_unref (dst_texture);
      cogl_object_unref (offscreen);
    }

  winsys->set_gles2_context (gles2_ctx, NULL);

  /* From what I understand of the GL spec, changes to a shared object
   * are not guaranteed to be propagated to another context until that
   * object is rebound in that context so we can just rebind it
   * here */
  gles2_ctx->vtable->glBindTexture (GL_TEXTURE_2D, tex_id);
}

/* We wrap glBindFramebuffer so that when framebuffer 0 is bound
 * we can instead bind the write_framebuffer passed to
 * cogl_push_gles2_context().
 */
static void
gl_bind_framebuffer_wrapper (GLenum target, GLuint framebuffer)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;

  gles2_ctx->current_fbo_handle = framebuffer;

  if (framebuffer == 0 && cogl_is_offscreen (gles2_ctx->write_buffer))
    {
      CoglGLES2Offscreen *write = gles2_ctx->gles2_write_buffer;
      framebuffer = write->gl_framebuffer.fbo_handle;
    }

  gles2_ctx->context->glBindFramebuffer (target, framebuffer);

  update_current_flip_state (gles2_ctx);
}

static int
transient_bind_read_buffer (CoglGLES2Context *gles2_ctx)
{
  if (gles2_ctx->current_fbo_handle == 0)
    {
      if (cogl_is_offscreen (gles2_ctx->read_buffer))
        {
          CoglGLES2Offscreen *read = gles2_ctx->gles2_read_buffer;
          GLuint read_fbo_handle = read->gl_framebuffer.fbo_handle;

          gles2_ctx->context->glBindFramebuffer (GL_FRAMEBUFFER,
                                                 read_fbo_handle);

          return RESTORE_FB_FROM_OFFSCREEN;
        }
      else
        {
          _cogl_framebuffer_gl_bind (gles2_ctx->read_buffer,
                                     0 /* target ignored */);

          return RESTORE_FB_FROM_ONSCREEN;
        }
    }
  else
    return RESTORE_FB_NONE;
}

static void
restore_write_buffer (CoglGLES2Context *gles2_ctx,
                      int restore_mode)
{
  switch (restore_mode)
    {
    case RESTORE_FB_FROM_OFFSCREEN:

      gl_bind_framebuffer_wrapper (GL_FRAMEBUFFER, 0);

      break;
    case RESTORE_FB_FROM_ONSCREEN:

      /* Note: we can't restore the original write buffer using
       * _cogl_framebuffer_gl_bind() if it's an offscreen
       * framebuffer because _cogl_framebuffer_gl_bind() doesn't
       * know about the fbo handle owned by the gles2 context.
       */
      if (cogl_is_offscreen (gles2_ctx->write_buffer))
        gl_bind_framebuffer_wrapper (GL_FRAMEBUFFER, 0);
      else
        _cogl_framebuffer_gl_bind (gles2_ctx->write_buffer, GL_FRAMEBUFFER);

      break;
    case RESTORE_FB_NONE:
      break;
    }
}

/* We wrap glReadPixels so when framebuffer 0 is bound then we can
 * read from the read_framebuffer passed to cogl_push_gles2_context().
 */
static void
gl_read_pixels_wrapper (GLint x,
                        GLint y,
                        GLsizei width,
                        GLsizei height,
                        GLenum format,
                        GLenum type,
                        GLvoid *pixels)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;
  int restore_mode = transient_bind_read_buffer (gles2_ctx);

  gles2_ctx->context->glReadPixels (x, y, width, height, format, type, pixels);

  restore_write_buffer (gles2_ctx, restore_mode);

  /* If the read buffer is a CoglOffscreen then the data will be
   * upside down compared to what GL expects so we need to flip it */
  if (gles2_ctx->current_fbo_handle == 0 &&
      cogl_is_offscreen (gles2_ctx->read_buffer))
    {
      int bpp, bytes_per_row, stride, y;
      uint8_t *bytes = pixels;
      uint8_t *temprow;

      /* Try to determine the bytes per pixel for the given
       * format/type combination. If there's a format which doesn't
       * make sense then we'll just give up because GL will probably
       * have just thrown an error */
      switch (format)
        {
        case GL_RGB:
          switch (type)
            {
            case GL_UNSIGNED_BYTE:
              bpp = 3;
              break;

            case GL_UNSIGNED_SHORT_5_6_5:
              bpp = 2;
              break;

            default:
              return;
            }
          break;

        case GL_RGBA:
          switch (type)
            {
            case GL_UNSIGNED_BYTE:
              bpp = 4;
              break;

            case GL_UNSIGNED_SHORT_4_4_4_4:
            case GL_UNSIGNED_SHORT_5_5_5_1:
              bpp = 2;
              break;

            default:
              return;
            }
          break;

        case GL_ALPHA:
          switch (type)
            {
            case GL_UNSIGNED_BYTE:
              bpp = 1;
              break;

            default:
              return;
            }
          break;

        default:
          return;
        }

      bytes_per_row = bpp * width;
      stride = ((bytes_per_row + gles2_ctx->pack_alignment - 1) &
                ~(gles2_ctx->pack_alignment - 1));
      temprow = g_alloca (bytes_per_row);

      /* vertically flip the buffer in-place */
      for (y = 0; y < height / 2; y++)
        {
          if (y != height - y - 1) /* skip center row */
            {
              memcpy (temprow,
                      bytes + y * stride,
                      bytes_per_row);
              memcpy (bytes + y * stride,
                      bytes + (height - y - 1) * stride,
                      bytes_per_row);
              memcpy (bytes + (height - y - 1) * stride,
                      temprow,
                      bytes_per_row);
            }
        }
    }
}

static void
gl_copy_tex_image_2d_wrapper (GLenum target,
                              GLint level,
                              GLenum internal_format,
                              GLint x,
                              GLint y,
                              GLsizei width,
                              GLsizei height,
                              GLint border)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;

  /* If we are reading from a CoglOffscreen buffer then the image will
   * be upside down with respect to what GL expects so we can't use
   * glCopyTexImage2D. Instead we we'll try to use the Cogl API to
   * flip it */
  if (gles2_ctx->current_fbo_handle == 0 &&
      cogl_is_offscreen (gles2_ctx->read_buffer))
    {
      /* This will only work with the GL_TEXTURE_2D target. FIXME:
       * GLES2 also supports setting cube map textures with
       * glTexImage2D so we need to handle that too */
      if (target != GL_TEXTURE_2D)
        return;

      /* Create an empty texture to hold the data */
      gles2_ctx->vtable->glTexImage2D (target,
                                       level,
                                       internal_format,
                                       width, height,
                                       border,
                                       internal_format, /* format */
                                       GL_UNSIGNED_BYTE, /* type */
                                       NULL /* data */);

      copy_flipped_texture (gles2_ctx,
                            level,
                            x, y, /* src_x/src_y */
                            0, 0, /* dst_x/dst_y */
                            width, height);
    }
  else
    {
      int restore_mode = transient_bind_read_buffer (gles2_ctx);

      gles2_ctx->context->glCopyTexImage2D (target, level, internal_format,
                                            x, y, width, height, border);

      restore_write_buffer (gles2_ctx, restore_mode);

      set_texture_object_data (gles2_ctx,
                               target,
                               level,
                               internal_format,
                               width, height);
    }
}

static void
gl_copy_tex_sub_image_2d_wrapper (GLenum target,
                                  GLint level,
                                  GLint xoffset,
                                  GLint yoffset,
                                  GLint x,
                                  GLint y,
                                  GLsizei width,
                                  GLsizei height)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;

  /* If we are reading from a CoglOffscreen buffer then the image will
   * be upside down with respect to what GL expects so we can't use
   * glCopyTexSubImage2D. Instead we we'll try to use the Cogl API to
   * flip it */
  if (gles2_ctx->current_fbo_handle == 0 &&
      cogl_is_offscreen (gles2_ctx->read_buffer))
    {
      /* This will only work with the GL_TEXTURE_2D target. FIXME:
       * GLES2 also supports setting cube map textures with
       * glTexImage2D so we need to handle that too */
      if (target != GL_TEXTURE_2D)
        return;

      copy_flipped_texture (gles2_ctx,
                            level,
                            x, y, /* src_x/src_y */
                            xoffset, yoffset, /* dst_x/dst_y */
                            width, height);
    }
  else
    {
      int restore_mode = transient_bind_read_buffer (gles2_ctx);

      gles2_ctx->context->glCopyTexSubImage2D (target, level,
                                               xoffset, yoffset,
                                               x, y, width, height);

      restore_write_buffer (gles2_ctx, restore_mode);
    }
}

static GLuint
gl_create_shader_wrapper (GLenum type)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;
  GLuint id;

  id = gles2_ctx->context->glCreateShader (type);

  if (id != 0)
    {
      CoglGLES2ShaderData *data = g_slice_new (CoglGLES2ShaderData);

      data->object_id = id;
      data->type = type;
      data->ref_count = 1;
      data->deleted = FALSE;

      g_hash_table_insert (gles2_ctx->shader_map,
                           GINT_TO_POINTER (id),
                           data);
    }

  return id;
}

static void
gl_delete_shader_wrapper (GLuint shader)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;
  CoglGLES2ShaderData *shader_data;

  if ((shader_data = g_hash_table_lookup (gles2_ctx->shader_map,
                                          GINT_TO_POINTER (shader))) &&
      !shader_data->deleted)
    {
      shader_data->deleted = TRUE;
      shader_data_unref (gles2_ctx, shader_data);
    }

  gles2_ctx->context->glDeleteShader (shader);
}

static GLuint
gl_create_program_wrapper (void)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;
  GLuint id;

  id = gles2_ctx->context->glCreateProgram ();

  if (id != 0)
    {
      CoglGLES2ProgramData *data = g_slice_new (CoglGLES2ProgramData);

      data->object_id = id;
      data->attached_shaders = NULL;
      data->ref_count = 1;
      data->deleted = FALSE;
      data->context = gles2_ctx;
      data->flip_vector_location = 0;
      data->flip_vector_state = COGL_GLES2_FLIP_STATE_UNKNOWN;

      g_hash_table_insert (gles2_ctx->program_map,
                           GINT_TO_POINTER (id),
                           data);
    }

  return id;
}

static void
gl_delete_program_wrapper (GLuint program)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;
  CoglGLES2ProgramData *program_data;

  if ((program_data = g_hash_table_lookup (gles2_ctx->program_map,
                                           GINT_TO_POINTER (program))) &&
      !program_data->deleted)
    {
      program_data->deleted = TRUE;
      program_data_unref (program_data);
    }

  gles2_ctx->context->glDeleteProgram (program);
}

static void
gl_use_program_wrapper (GLuint program)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;
  CoglGLES2ProgramData *program_data;

  program_data = g_hash_table_lookup (gles2_ctx->program_map,
                                      GINT_TO_POINTER (program));

  if (program_data)
    program_data->ref_count++;
  if (gles2_ctx->current_program)
    program_data_unref (gles2_ctx->current_program);

  gles2_ctx->current_program = program_data;

  gles2_ctx->context->glUseProgram (program);
}

static void
gl_attach_shader_wrapper (GLuint program,
                          GLuint shader)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;
  CoglGLES2ProgramData *program_data;
  CoglGLES2ShaderData *shader_data;

  if ((program_data = g_hash_table_lookup (gles2_ctx->program_map,
                                           GINT_TO_POINTER (program))) &&
      (shader_data = g_hash_table_lookup (gles2_ctx->shader_map,
                                          GINT_TO_POINTER (shader))) &&
      /* Ignore attempts to attach a shader that is already attached */
      g_list_find (program_data->attached_shaders, shader_data) == NULL)
    {
      shader_data->ref_count++;
      program_data->attached_shaders =
        g_list_prepend (program_data->attached_shaders, shader_data);
    }

  gles2_ctx->context->glAttachShader (program, shader);
}

static void
gl_detach_shader_wrapper (GLuint program,
                          GLuint shader)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;
  CoglGLES2ProgramData *program_data;
  CoglGLES2ShaderData *shader_data;

  if ((program_data = g_hash_table_lookup (gles2_ctx->program_map,
                                           GINT_TO_POINTER (program))) &&
      (shader_data = g_hash_table_lookup (gles2_ctx->shader_map,
                                          GINT_TO_POINTER (shader))))
    detach_shader (program_data, shader_data);

  gles2_ctx->context->glDetachShader (program, shader);
}

static void
gl_shader_source_wrapper (GLuint shader,
                          GLsizei count,
                          const char *const *string,
                          const GLint *length)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;
  CoglGLES2ShaderData *shader_data;

  if ((shader_data = g_hash_table_lookup (gles2_ctx->shader_map,
                                          GINT_TO_POINTER (shader))) &&
      shader_data->type == GL_VERTEX_SHADER)
    {
      char **string_copy = g_alloca ((count + 1) * sizeof (char *));
      int *length_copy = g_alloca ((count + 1) * sizeof (int));
      int i;

      /* Replace any occurences of the symbol 'main' with a different
       * symbol so that we can provide our own wrapper main
       * function */

      for (i = 0; i < count; i++)
        {
          int string_length;

          if (length == NULL || length[i] < 0)
            string_length = strlen (string[i]);
          else
            string_length = length[i];

          string_copy[i] = g_memdup (string[i], string_length);

          replace_token (string_copy[i],
                         "main",
                         MAIN_WRAPPER_REPLACEMENT_NAME,
                         string_length);

          length_copy[i] = string_length;
        }

      string_copy[count] = (char *) main_wrapper_function;
      length_copy[count] = sizeof (main_wrapper_function) - 1;

      gles2_ctx->context->glShaderSource (shader,
                                          count + 1,
                                          (const char *const *) string_copy,
                                          length_copy);

      /* Note: we don't need to free the last entry in string_copy[]
       * because it is our static wrapper string... */
      for (i = 0; i < count; i++)
        g_free (string_copy[i]);
    }
  else
    gles2_ctx->context->glShaderSource (shader, count, string, length);
}

static void
gl_get_shader_source_wrapper (GLuint shader,
                              GLsizei buf_size,
                              GLsizei *length_out,
                              GLchar *source)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;
  CoglGLES2ShaderData *shader_data;
  GLsizei length;

  gles2_ctx->context->glGetShaderSource (shader,
                                         buf_size,
                                         &length,
                                         source);

  if ((shader_data = g_hash_table_lookup (gles2_ctx->shader_map,
                                          GINT_TO_POINTER (shader))) &&
      shader_data->type == GL_VERTEX_SHADER)
    {
      GLsizei copy_length = MIN (length, buf_size - 1);
      static const char wrapper_marker[] = MAIN_WRAPPER_BEGIN;
      char *wrapper_start;

      /* Strip out the wrapper snippet we added when the source was
       * specified */
      wrapper_start = _cogl_util_memmem (source,
                                         copy_length,
                                         wrapper_marker,
                                         sizeof (wrapper_marker) - 1);
      if (wrapper_start)
        {
          length = wrapper_start - source;
          copy_length = length;
          *wrapper_start = '\0';
        }

      /* Correct the name of the main function back to its original */
      replace_token (source,
                     MAIN_WRAPPER_REPLACEMENT_NAME,
                     "main",
                     copy_length);
    }

  if (length_out)
    *length_out = length;
}

static void
gl_link_program_wrapper (GLuint program)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;
  CoglGLES2ProgramData *program_data;

  gles2_ctx->context->glLinkProgram (program);

  program_data = g_hash_table_lookup (gles2_ctx->program_map,
                                      GINT_TO_POINTER (program));

  if (program_data)
    {
      GLint status;

      gles2_ctx->context->glGetProgramiv (program, GL_LINK_STATUS, &status);

      if (status)
        program_data->flip_vector_location =
          gles2_ctx->context->glGetUniformLocation (program,
                                                    MAIN_WRAPPER_FLIP_UNIFORM);
    }
}

static void
gl_get_program_iv_wrapper (GLuint program,
                           GLenum pname,
                           GLint *params)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;

  gles2_ctx->context->glGetProgramiv (program, pname, params);

  switch (pname)
    {
    case GL_ATTACHED_SHADERS:
      /* Decrease the number of shaders to try and hide the shader
       * wrapper we added */
      if (*params > 1)
        (*params)--;
      break;
    }
}

static void
flush_viewport_state (CoglGLES2Context *gles2_ctx)
{
  if (gles2_ctx->viewport_dirty)
    {
      int y;

      if (gles2_ctx->current_flip_state == COGL_GLES2_FLIP_STATE_FLIPPED)
        {
          /* We need to know the height of the current framebuffer in
           * order to flip the viewport. Fortunately we don't need to
           * track the height of the FBOs created within the GLES2
           * context because we would never be flipping if they are
           * bound so we can just assume Cogl's framebuffer is bound
           * when we are flipping */
          int fb_height = cogl_framebuffer_get_height (gles2_ctx->write_buffer);
          y = fb_height - (gles2_ctx->viewport[1] + gles2_ctx->viewport[3]);
        }
      else
        y = gles2_ctx->viewport[1];

      gles2_ctx->context->glViewport (gles2_ctx->viewport[0],
                                      y,
                                      gles2_ctx->viewport[2],
                                      gles2_ctx->viewport[3]);

      gles2_ctx->viewport_dirty = FALSE;
    }
}

static void
flush_scissor_state (CoglGLES2Context *gles2_ctx)
{
  if (gles2_ctx->scissor_dirty)
    {
      int y;

      if (gles2_ctx->current_flip_state == COGL_GLES2_FLIP_STATE_FLIPPED)
        {
          /* See comment above about the viewport flipping */
          int fb_height = cogl_framebuffer_get_height (gles2_ctx->write_buffer);
          y = fb_height - (gles2_ctx->scissor[1] + gles2_ctx->scissor[3]);
        }
      else
        y = gles2_ctx->scissor[1];

      gles2_ctx->context->glScissor (gles2_ctx->scissor[0],
                                     y,
                                     gles2_ctx->scissor[2],
                                     gles2_ctx->scissor[3]);

      gles2_ctx->scissor_dirty = FALSE;
    }
}

static void
flush_front_face_state (CoglGLES2Context *gles2_ctx)
{
  if (gles2_ctx->front_face_dirty)
    {
      GLenum front_face;

      if (gles2_ctx->current_flip_state == COGL_GLES2_FLIP_STATE_FLIPPED)
        {
          if (gles2_ctx->front_face == GL_CW)
            front_face = GL_CCW;
          else
            front_face = GL_CW;
        }
      else
        front_face = gles2_ctx->front_face;

      gles2_ctx->context->glFrontFace (front_face);

      gles2_ctx->front_face_dirty = FALSE;
    }
}

static void
pre_draw_wrapper (CoglGLES2Context *gles2_ctx)
{
  /* If there's no current program then we'll just let GL report an
   * error */
  if (gles2_ctx->current_program == NULL)
    return;

  flush_viewport_state (gles2_ctx);
  flush_scissor_state (gles2_ctx);
  flush_front_face_state (gles2_ctx);

  /* We want to flip rendering when the application is rendering to a
   * Cogl offscreen buffer in order to maintain the flipped texture
   * coordinate origin */
  if (gles2_ctx->current_flip_state !=
      gles2_ctx->current_program->flip_vector_state)
    {
      GLuint location =
        gles2_ctx->current_program->flip_vector_location;
      float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f };

      if (gles2_ctx->current_flip_state == COGL_GLES2_FLIP_STATE_FLIPPED)
        value[1] = -1.0f;

      gles2_ctx->context->glUniform4fv (location, 1, value);

      gles2_ctx->current_program->flip_vector_state =
        gles2_ctx->current_flip_state;
    }
}

static void
gl_clear_wrapper (GLbitfield mask)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;

  /* Clearing is affected by the scissor state so we need to ensure
   * that's flushed */
  flush_scissor_state (gles2_ctx);

  gles2_ctx->context->glClear (mask);
}

static void
gl_draw_elements_wrapper (GLenum mode,
                          GLsizei count,
                          GLenum type,
                          const GLvoid *indices)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;

  pre_draw_wrapper (gles2_ctx);

  gles2_ctx->context->glDrawElements (mode, count, type, indices);
}

static void
gl_draw_arrays_wrapper (GLenum mode,
                        GLint first,
                        GLsizei count)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;

  pre_draw_wrapper (gles2_ctx);

  gles2_ctx->context->glDrawArrays (mode, first, count);
}

static void
gl_get_program_info_log_wrapper (GLuint program,
                                 GLsizei buf_size,
                                 GLsizei *length_out,
                                 GLchar *info_log)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;
  GLsizei length;

  gles2_ctx->context->glGetProgramInfoLog (program,
                                           buf_size,
                                           &length,
                                           info_log);

  replace_token (info_log,
                 MAIN_WRAPPER_REPLACEMENT_NAME,
                 "main",
                 MIN (length, buf_size));

  if (length_out)
    *length_out = length;
}

static void
gl_get_shader_info_log_wrapper (GLuint shader,
                                GLsizei buf_size,
                                GLsizei *length_out,
                                GLchar *info_log)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;
  GLsizei length;

  gles2_ctx->context->glGetShaderInfoLog (shader,
                                          buf_size,
                                          &length,
                                          info_log);

  replace_token (info_log,
                 MAIN_WRAPPER_REPLACEMENT_NAME,
                 "main",
                 MIN (length, buf_size));

  if (length_out)
    *length_out = length;
}

static void
gl_front_face_wrapper (GLenum mode)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;

  /* If the mode doesn't make any sense then we'll just let the
   * context deal with it directly so that it will throw an error */
  if (mode != GL_CW && mode != GL_CCW)
    gles2_ctx->context->glFrontFace (mode);
  else
    {
      gles2_ctx->front_face = mode;
      gles2_ctx->front_face_dirty = TRUE;
    }
}

static void
gl_viewport_wrapper (GLint x,
                     GLint y,
                     GLsizei width,
                     GLsizei height)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;

  /* If the viewport is invalid then we'll just let the context deal
   * with it directly so that it will throw an error */
  if (width < 0 || height < 0)
    gles2_ctx->context->glViewport (x, y, width, height);
  else
    {
      gles2_ctx->viewport[0] = x;
      gles2_ctx->viewport[1] = y;
      gles2_ctx->viewport[2] = width;
      gles2_ctx->viewport[3] = height;
      gles2_ctx->viewport_dirty = TRUE;
    }
}

static void
gl_scissor_wrapper (GLint x,
                    GLint y,
                    GLsizei width,
                    GLsizei height)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;

  /* If the scissor is invalid then we'll just let the context deal
   * with it directly so that it will throw an error */
  if (width < 0 || height < 0)
    gles2_ctx->context->glScissor (x, y, width, height);
  else
    {
      gles2_ctx->scissor[0] = x;
      gles2_ctx->scissor[1] = y;
      gles2_ctx->scissor[2] = width;
      gles2_ctx->scissor[3] = height;
      gles2_ctx->scissor_dirty = TRUE;
    }
}

static void
gl_get_boolean_v_wrapper (GLenum pname,
                          GLboolean *params)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;

  switch (pname)
    {
    case GL_VIEWPORT:
      {
        int i;

        for (i = 0; i < 4; i++)
          params[i] = !!gles2_ctx->viewport[i];
      }
      break;

    case GL_SCISSOR_BOX:
      {
        int i;

        for (i = 0; i < 4; i++)
          params[i] = !!gles2_ctx->scissor[i];
      }
      break;

    default:
      gles2_ctx->context->glGetBooleanv (pname, params);
    }
}

static void
gl_get_integer_v_wrapper (GLenum pname,
                          GLint *params)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;

  switch (pname)
    {
    case GL_VIEWPORT:
      {
        int i;

        for (i = 0; i < 4; i++)
          params[i] = gles2_ctx->viewport[i];
      }
      break;

    case GL_SCISSOR_BOX:
      {
        int i;

        for (i = 0; i < 4; i++)
          params[i] = gles2_ctx->scissor[i];
      }
      break;

    case GL_FRONT_FACE:
      params[0] = gles2_ctx->front_face;
      break;

    default:
      gles2_ctx->context->glGetIntegerv (pname, params);
    }
}

static void
gl_get_float_v_wrapper (GLenum pname,
                        GLfloat *params)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;

  switch (pname)
    {
    case GL_VIEWPORT:
      {
        int i;

        for (i = 0; i < 4; i++)
          params[i] = gles2_ctx->viewport[i];
      }
      break;

    case GL_SCISSOR_BOX:
      {
        int i;

        for (i = 0; i < 4; i++)
          params[i] = gles2_ctx->scissor[i];
      }
      break;

    case GL_FRONT_FACE:
      params[0] = gles2_ctx->front_face;
      break;

    default:
      gles2_ctx->context->glGetFloatv (pname, params);
    }
}

static void
gl_pixel_store_i_wrapper (GLenum pname, GLint param)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;

  gles2_ctx->context->glPixelStorei (pname, param);

  if (pname == GL_PACK_ALIGNMENT &&
      (param == 1 || param == 2 || param == 4 || param == 8))
    gles2_ctx->pack_alignment = param;
}

static void
gl_active_texture_wrapper (GLenum texture)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;
  int texture_unit;

  gles2_ctx->context->glActiveTexture (texture);

  texture_unit = texture - GL_TEXTURE0;

  /* If the application is binding some odd looking texture unit
   * numbers then we'll just ignore it and hope that GL has generated
   * an error */
  if (texture_unit >= 0 && texture_unit < 512)
    {
      gles2_ctx->current_texture_unit = texture_unit;
      g_array_set_size (gles2_ctx->texture_units,
                        MAX (texture_unit, gles2_ctx->texture_units->len));
    }
}

static void
gl_delete_textures_wrapper (GLsizei n,
                            const GLuint *textures)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;
  int texture_index;
  int texture_unit;

  gles2_ctx->context->glDeleteTextures (n, textures);

  for (texture_index = 0; texture_index < n; texture_index++)
    {
      /* Reset any texture units that have any of these textures bound */
      for (texture_unit = 0;
           texture_unit < gles2_ctx->texture_units->len;
           texture_unit++)
        {
          CoglGLES2TextureUnitData *unit =
            &g_array_index (gles2_ctx->texture_units,
                            CoglGLES2TextureUnitData,
                            texture_unit);

          if (unit->current_texture_2d == textures[texture_index])
            unit->current_texture_2d = 0;
        }

      /* Remove the binding. We can do this immediately because unlike
       * shader objects the deletion isn't delayed until the object is
       * unbound */
      g_hash_table_remove (gles2_ctx->texture_object_map,
                           GUINT_TO_POINTER (textures[texture_index]));
    }
}

static void
gl_bind_texture_wrapper (GLenum target,
                         GLuint texture)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;

  gles2_ctx->context->glBindTexture (target, texture);

  if (target == GL_TEXTURE_2D)
    {
      CoglGLES2TextureUnitData *unit =
        &g_array_index (gles2_ctx->texture_units,
                        CoglGLES2TextureUnitData,
                        gles2_ctx->current_texture_unit);
      unit->current_texture_2d = texture;
    }
}

static void
gl_tex_image_2d_wrapper (GLenum target,
                         GLint level,
                         GLint internal_format,
                         GLsizei width,
                         GLsizei height,
                         GLint border,
                         GLenum format,
                         GLenum type,
                         const GLvoid *pixels)
{
  CoglGLES2Context *gles2_ctx = current_gles2_context;

  gles2_ctx->context->glTexImage2D (target,
                                    level,
                                    internal_format,
                                    width, height,
                                    border,
                                    format,
                                    type,
                                    pixels);

  set_texture_object_data (gles2_ctx,
                           target,
                           level,
                           internal_format,
                           width, height);
}

static void
_cogl_gles2_offscreen_free (CoglGLES2Offscreen *gles2_offscreen)
{
  _cogl_list_remove (&gles2_offscreen->link);
  g_slice_free (CoglGLES2Offscreen, gles2_offscreen);
}

static void
force_delete_program_object (CoglGLES2Context *context,
                             CoglGLES2ProgramData *program_data)
{
  if (!program_data->deleted)
    {
      context->context->glDeleteProgram (program_data->object_id);
      program_data->deleted = TRUE;
      program_data_unref (program_data);
    }
}

static void
force_delete_shader_object (CoglGLES2Context *context,
                            CoglGLES2ShaderData *shader_data)
{
  if (!shader_data->deleted)
    {
      context->context->glDeleteShader (shader_data->object_id);
      shader_data->deleted = TRUE;
      shader_data_unref (context, shader_data);
    }
}

static void
force_delete_texture_object (CoglGLES2Context *context,
                             CoglGLES2TextureObjectData *texture_data)
{
  context->context->glDeleteTextures (1, &texture_data->object_id);
}

static void
_cogl_gles2_context_free (CoglGLES2Context *gles2_context)
{
  CoglContext *ctx = gles2_context->context;
  const CoglWinsysVtable *winsys;
  GList *objects, *l;

  if (gles2_context->current_program)
    program_data_unref (gles2_context->current_program);

  /* Try to forcibly delete any shaders, programs and textures so that
   * they won't get leaked. Because all GLES2 contexts are in the same
   * share list as Cogl's context these won't get deleted by default.
   * FIXME: we should do this for all of the other resources too, like
   * textures */
  objects = g_hash_table_get_values (gles2_context->program_map);
  for (l = objects; l; l = l->next)
    force_delete_program_object (gles2_context, l->data);
  g_list_free (objects);
  objects = g_hash_table_get_values (gles2_context->shader_map);
  for (l = objects; l; l = l->next)
    force_delete_shader_object (gles2_context, l->data);
  g_list_free (objects);
  objects = g_hash_table_get_values (gles2_context->texture_object_map);
  for (l = objects; l; l = l->next)
    force_delete_texture_object (gles2_context, l->data);
  g_list_free (objects);

  /* All of the program and shader objects should now be destroyed */
  if (g_hash_table_size (gles2_context->program_map) > 0)
    g_warning ("Program objects have been leaked from a CoglGLES2Context");
  if (g_hash_table_size (gles2_context->shader_map) > 0)
    g_warning ("Shader objects have been leaked from a CoglGLES2Context");

  g_hash_table_destroy (gles2_context->program_map);
  g_hash_table_destroy (gles2_context->shader_map);

  g_hash_table_destroy (gles2_context->texture_object_map);
  g_array_free (gles2_context->texture_units, TRUE);

  winsys = ctx->display->renderer->winsys_vtable;
  winsys->destroy_gles2_context (gles2_context);

  while (!_cogl_list_empty (&gles2_context->foreign_offscreens))
    {
      CoglGLES2Offscreen *gles2_offscreen =
        _cogl_container_of (gles2_context->foreign_offscreens.next,
                            CoglGLES2Offscreen,
                            link);

      /* Note: this will also indirectly free the gles2_offscreen by
       * calling the destroy notify for the _user_data */
      cogl_object_set_user_data (COGL_OBJECT (gles2_offscreen->original_offscreen),
                                 &offscreen_wrapper_key,
                                 NULL,
                                 NULL);
    }

  g_free (gles2_context->vtable);

  g_free (gles2_context);
}

static void
free_shader_data (CoglGLES2ShaderData *data)
{
  g_slice_free (CoglGLES2ShaderData, data);
}

static void
free_program_data (CoglGLES2ProgramData *data)
{
  while (data->attached_shaders)
    detach_shader (data,
                   data->attached_shaders->data);

  g_slice_free (CoglGLES2ProgramData, data);
}

static void
free_texture_object_data (CoglGLES2TextureObjectData *data)
{
  g_slice_free (CoglGLES2TextureObjectData, data);
}

CoglGLES2Context *
cogl_gles2_context_new (CoglContext *ctx, CoglError **error)
{
  CoglGLES2Context *gles2_ctx;
  const CoglWinsysVtable *winsys;

  if (!cogl_has_feature (ctx, COGL_FEATURE_ID_GLES2_CONTEXT))
    {
      _cogl_set_error (error, COGL_GLES2_CONTEXT_ERROR,
                   COGL_GLES2_CONTEXT_ERROR_UNSUPPORTED,
                   "Backend doesn't support creating GLES2 contexts");

      return NULL;
    }

  gles2_ctx = g_malloc0 (sizeof (CoglGLES2Context));

  gles2_ctx->context = ctx;

  _cogl_list_init (&gles2_ctx->foreign_offscreens);

  winsys = ctx->display->renderer->winsys_vtable;
  gles2_ctx->winsys = winsys->context_create_gles2_context (ctx, error);
  if (gles2_ctx->winsys == NULL)
    {
      g_free (gles2_ctx);
      return NULL;
    }

  gles2_ctx->current_flip_state = COGL_GLES2_FLIP_STATE_UNKNOWN;
  gles2_ctx->viewport_dirty = TRUE;
  gles2_ctx->scissor_dirty = TRUE;
  gles2_ctx->front_face_dirty = TRUE;
  gles2_ctx->front_face = GL_CCW;
  gles2_ctx->pack_alignment = 4;

  gles2_ctx->vtable = g_malloc0 (sizeof (CoglGLES2Vtable));
#define COGL_EXT_BEGIN(name, \
                       min_gl_major, min_gl_minor, \
                       gles_availability, \
                       extension_suffixes, extension_names)

#define COGL_EXT_FUNCTION(ret, name, args) \
  gles2_ctx->vtable->name = (void *) ctx->name;

#define COGL_EXT_END()

#include "gl-prototypes/cogl-gles2-functions.h"

#undef COGL_EXT_BEGIN
#undef COGL_EXT_FUNCTION
#undef COGL_EXT_END

  gles2_ctx->vtable->glBindFramebuffer =
    (void *) gl_bind_framebuffer_wrapper;
  gles2_ctx->vtable->glReadPixels =
    (void *) gl_read_pixels_wrapper;
  gles2_ctx->vtable->glCopyTexImage2D =
    (void *) gl_copy_tex_image_2d_wrapper;
  gles2_ctx->vtable->glCopyTexSubImage2D =
    (void *) gl_copy_tex_sub_image_2d_wrapper;

  gles2_ctx->vtable->glCreateShader = gl_create_shader_wrapper;
  gles2_ctx->vtable->glDeleteShader = gl_delete_shader_wrapper;
  gles2_ctx->vtable->glCreateProgram = gl_create_program_wrapper;
  gles2_ctx->vtable->glDeleteProgram = gl_delete_program_wrapper;
  gles2_ctx->vtable->glUseProgram = gl_use_program_wrapper;
  gles2_ctx->vtable->glAttachShader = gl_attach_shader_wrapper;
  gles2_ctx->vtable->glDetachShader = gl_detach_shader_wrapper;
  gles2_ctx->vtable->glShaderSource = gl_shader_source_wrapper;
  gles2_ctx->vtable->glGetShaderSource = gl_get_shader_source_wrapper;
  gles2_ctx->vtable->glLinkProgram = gl_link_program_wrapper;
  gles2_ctx->vtable->glGetProgramiv = gl_get_program_iv_wrapper;
  gles2_ctx->vtable->glGetProgramInfoLog = gl_get_program_info_log_wrapper;
  gles2_ctx->vtable->glGetShaderInfoLog = gl_get_shader_info_log_wrapper;
  gles2_ctx->vtable->glClear = gl_clear_wrapper;
  gles2_ctx->vtable->glDrawElements = gl_draw_elements_wrapper;
  gles2_ctx->vtable->glDrawArrays = gl_draw_arrays_wrapper;
  gles2_ctx->vtable->glFrontFace = gl_front_face_wrapper;
  gles2_ctx->vtable->glViewport = gl_viewport_wrapper;
  gles2_ctx->vtable->glScissor = gl_scissor_wrapper;
  gles2_ctx->vtable->glGetBooleanv = gl_get_boolean_v_wrapper;
  gles2_ctx->vtable->glGetIntegerv = gl_get_integer_v_wrapper;
  gles2_ctx->vtable->glGetFloatv = gl_get_float_v_wrapper;
  gles2_ctx->vtable->glPixelStorei = gl_pixel_store_i_wrapper;
  gles2_ctx->vtable->glActiveTexture = gl_active_texture_wrapper;
  gles2_ctx->vtable->glDeleteTextures = gl_delete_textures_wrapper;
  gles2_ctx->vtable->glBindTexture = gl_bind_texture_wrapper;
  gles2_ctx->vtable->glTexImage2D = gl_tex_image_2d_wrapper;

  gles2_ctx->shader_map =
    g_hash_table_new_full (g_direct_hash,
                           g_direct_equal,
                           NULL, /* key_destroy */
                           (GDestroyNotify) free_shader_data);
  gles2_ctx->program_map =
    g_hash_table_new_full (g_direct_hash,
                           g_direct_equal,
                           NULL, /* key_destroy */
                           (GDestroyNotify) free_program_data);

  gles2_ctx->texture_object_map =
    g_hash_table_new_full (g_direct_hash,
                           g_direct_equal,
                           NULL, /* key_destroy */
                           (GDestroyNotify) free_texture_object_data);

  gles2_ctx->texture_units = g_array_new (FALSE, /* not zero terminated */
                                          TRUE, /* clear */
                                          sizeof (CoglGLES2TextureUnitData));
  gles2_ctx->current_texture_unit = 0;
  g_array_set_size (gles2_ctx->texture_units, 1);

  return _cogl_gles2_context_object_new (gles2_ctx);
}

const CoglGLES2Vtable *
cogl_gles2_context_get_vtable (CoglGLES2Context *gles2_ctx)
{
  return gles2_ctx->vtable;
}

/* When drawing to a CoglFramebuffer from a separate context we have
 * to be able to allocate ancillary buffers for that context...
 */
static CoglGLES2Offscreen *
_cogl_gles2_offscreen_allocate (CoglOffscreen *offscreen,
                                CoglGLES2Context *gles2_context,
                                CoglError **error)
{
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (offscreen);
  const CoglWinsysVtable *winsys;
  CoglError *internal_error = NULL;
  CoglGLES2Offscreen *gles2_offscreen;
  int level_width;
  int level_height;

  if (!framebuffer->allocated &&
      !cogl_framebuffer_allocate (framebuffer, error))
    {
      return NULL;
    }

  _cogl_list_for_each (gles2_offscreen,
                       &gles2_context->foreign_offscreens,
                       link)
    {
      if (gles2_offscreen->original_offscreen == offscreen)
        return gles2_offscreen;
    }

  winsys = _cogl_framebuffer_get_winsys (framebuffer);
  winsys->save_context (framebuffer->context);
  if (!winsys->set_gles2_context (gles2_context, &internal_error))
    {
      winsys->restore_context (framebuffer->context);

      cogl_error_free (internal_error);
      _cogl_set_error (error, COGL_FRAMEBUFFER_ERROR,
                   COGL_FRAMEBUFFER_ERROR_ALLOCATE,
                   "Failed to bind gles2 context to create framebuffer");
      return NULL;
    }

  gles2_offscreen = g_slice_new0 (CoglGLES2Offscreen);

  _cogl_texture_get_level_size (offscreen->texture,
                                offscreen->texture_level,
                                &level_width,
                                &level_height,
                                NULL);

  if (!_cogl_framebuffer_try_creating_gl_fbo (gles2_context->context,
                                              offscreen->texture,
                                              offscreen->texture_level,
                                              level_width,
                                              level_height,
                                              offscreen->depth_texture,
                                              &COGL_FRAMEBUFFER (offscreen)->config,
                                              offscreen->allocation_flags,
                                              &gles2_offscreen->gl_framebuffer))
    {
      winsys->restore_context (framebuffer->context);

      g_slice_free (CoglGLES2Offscreen, gles2_offscreen);

      _cogl_set_error (error, COGL_FRAMEBUFFER_ERROR,
                   COGL_FRAMEBUFFER_ERROR_ALLOCATE,
                   "Failed to create an OpenGL framebuffer object");
      return NULL;
    }

  winsys->restore_context (framebuffer->context);

  gles2_offscreen->original_offscreen = offscreen;

  _cogl_list_insert (&gles2_context->foreign_offscreens,
                     &gles2_offscreen->link);

  /* So we avoid building up an ever growing collection of ancillary
   * buffers for wrapped framebuffers, we make sure that the wrappers
   * get freed when the original offscreen framebuffer is freed. */
  cogl_object_set_user_data (COGL_OBJECT (framebuffer),
                             &offscreen_wrapper_key,
                             gles2_offscreen,
                             (CoglUserDataDestroyCallback)
                                _cogl_gles2_offscreen_free);

  return gles2_offscreen;
}

CoglBool
cogl_push_gles2_context (CoglContext *ctx,
                         CoglGLES2Context *gles2_ctx,
                         CoglFramebuffer *read_buffer,
                         CoglFramebuffer *write_buffer,
                         CoglError **error)
{
  const CoglWinsysVtable *winsys = ctx->display->renderer->winsys_vtable;
  CoglError *internal_error = NULL;

  _COGL_RETURN_VAL_IF_FAIL (gles2_ctx != NULL, FALSE);

  /* The read/write buffers are properties of the gles2 context and we
   * don't currently track the read/write buffers as part of the stack
   * entries so we explicitly don't allow the same context to be
   * pushed multiple times. */
  if (g_queue_find (&ctx->gles2_context_stack, gles2_ctx))
    {
      g_critical ("Pushing the same GLES2 context multiple times isn't "
                  "supported");
      return FALSE;
    }

  if (ctx->gles2_context_stack.length == 0)
    {
      _cogl_journal_flush (read_buffer->journal);
      if (write_buffer != read_buffer)
        _cogl_journal_flush (write_buffer->journal);
      winsys->save_context (ctx);
    }
  else
    gles2_ctx->vtable->glFlush ();

  if (gles2_ctx->read_buffer != read_buffer)
    {
      if (cogl_is_offscreen (read_buffer))
        {
          gles2_ctx->gles2_read_buffer =
            _cogl_gles2_offscreen_allocate (COGL_OFFSCREEN (read_buffer),
                                            gles2_ctx,
                                            error);
          /* XXX: what consistency guarantees should this api have?
           *
           * It should be safe to return at this point but we provide
           * no guarantee to the caller whether their given buffers
           * may be referenced and old buffers unreferenced even
           * if the _push fails. */
          if (!gles2_ctx->gles2_read_buffer)
            return FALSE;
        }
      else
        gles2_ctx->gles2_read_buffer = NULL;
      if (gles2_ctx->read_buffer)
        cogl_object_unref (gles2_ctx->read_buffer);
      gles2_ctx->read_buffer = cogl_object_ref (read_buffer);
    }

  if (gles2_ctx->write_buffer != write_buffer)
    {
      if (cogl_is_offscreen (write_buffer))
        {
          gles2_ctx->gles2_write_buffer =
            _cogl_gles2_offscreen_allocate (COGL_OFFSCREEN (write_buffer),
                                            gles2_ctx,
                                            error);
          /* XXX: what consistency guarantees should this api have?
           *
           * It should be safe to return at this point but we provide
           * no guarantee to the caller whether their given buffers
           * may be referenced and old buffers unreferenced even
           * if the _push fails. */
          if (!gles2_ctx->gles2_write_buffer)
            return FALSE;
        }
      else
        gles2_ctx->gles2_write_buffer = NULL;
      if (gles2_ctx->write_buffer)
        cogl_object_unref (gles2_ctx->write_buffer);
      gles2_ctx->write_buffer = cogl_object_ref (write_buffer);

      update_current_flip_state (gles2_ctx);
    }

  if (!winsys->set_gles2_context (gles2_ctx, &internal_error))
    {
      winsys->restore_context (ctx);

      cogl_error_free (internal_error);
      _cogl_set_error (error, COGL_GLES2_CONTEXT_ERROR,
                   COGL_GLES2_CONTEXT_ERROR_DRIVER,
                   "Driver failed to make GLES2 context current");
      return FALSE;
    }

  g_queue_push_tail (&ctx->gles2_context_stack, gles2_ctx);

  /* The last time this context was pushed may have been with a
   * different offscreen draw framebuffer and so if GL framebuffer 0
   * is bound for this GLES2 context we may need to bind a new,
   * corresponding, window system framebuffer... */
  if (gles2_ctx->current_fbo_handle == 0)
    {
      if (cogl_is_offscreen (gles2_ctx->write_buffer))
        {
          CoglGLES2Offscreen *write = gles2_ctx->gles2_write_buffer;
          GLuint handle = write->gl_framebuffer.fbo_handle;
          gles2_ctx->context->glBindFramebuffer (GL_FRAMEBUFFER, handle);
        }
    }

  current_gles2_context = gles2_ctx;

  /* If this is the first time this gles2 context has been used then
   * we'll force the viewport and scissor to the right size. GL has
   * the semantics that the viewport and scissor default to the size
   * of the first surface the context is used with. If the first
   * CoglFramebuffer that this context is used with is an offscreen,
   * then the surface from GL's point of view will be the 1x1 dummy
   * surface so the viewport will be wrong. Therefore we just override
   * the default viewport and scissor here */
  if (!gles2_ctx->has_been_bound)
    {
      int fb_width = cogl_framebuffer_get_width (write_buffer);
      int fb_height = cogl_framebuffer_get_height (write_buffer);

      gles2_ctx->vtable->glViewport (0, 0, /* x/y */
                                     fb_width, fb_height);
      gles2_ctx->vtable->glScissor (0, 0, /* x/y */
                                    fb_width, fb_height);
      gles2_ctx->has_been_bound = TRUE;
    }

  return TRUE;
}

CoglGLES2Vtable *
cogl_gles2_get_current_vtable (void)
{
  return current_gles2_context ? current_gles2_context->vtable : NULL;
}

void
cogl_pop_gles2_context (CoglContext *ctx)
{
  CoglGLES2Context *gles2_ctx;
  const CoglWinsysVtable *winsys = ctx->display->renderer->winsys_vtable;

  _COGL_RETURN_IF_FAIL (ctx->gles2_context_stack.length > 0);

  g_queue_pop_tail (&ctx->gles2_context_stack);

  gles2_ctx = g_queue_peek_tail (&ctx->gles2_context_stack);

  if (gles2_ctx)
    {
      winsys->set_gles2_context (gles2_ctx, NULL);
      current_gles2_context = gles2_ctx;
    }
  else
    {
      winsys->restore_context (ctx);
      current_gles2_context = NULL;
    }
}

CoglTexture2D *
cogl_gles2_texture_2d_new_from_handle (CoglContext *ctx,
                                       CoglGLES2Context *gles2_ctx,
                                       unsigned int handle,
                                       int width,
                                       int height,
                                       CoglPixelFormat format)
{
  return cogl_texture_2d_gl_new_from_foreign (ctx,
                                              handle,
                                              width,
                                              height,
                                              format);
}

CoglBool
cogl_gles2_texture_get_handle (CoglTexture *texture,
                               unsigned int *handle,
                               unsigned int *target)
{
  return cogl_texture_get_gl_texture (texture, handle, target);
}
