#include "config.h"

#include "gskgldriverprivate.h"

#include "gskdebugprivate.h"
#include "gskprofilerprivate.h"
#include "gdk/gdkglcontextprivate.h"
#include "gdk/gdktextureprivate.h"
#include "gdk/gdkgltextureprivate.h"
#include "gdkmemorytextureprivate.h"

#include <gdk/gdk.h>
#include <epoxy/gl.h>

 typedef struct {
  GLuint fbo_id;
  GLuint depth_stencil_id;
} Fbo;

typedef struct {
  GLuint texture_id;
  int width;
  int height;
  GLuint min_filter;
  GLuint mag_filter;
  Fbo fbo;
  GdkTexture *user;
  guint in_use : 1;
  guint permanent : 1;

  /* TODO: Make this optional and not for every texture... */
  TextureSlice *slices;
  guint n_slices;
} Texture;

struct _GskGLDriver
{
  GObject parent_instance;

  GdkGLContext *gl_context;
  GskProfiler *profiler;
  struct {
    GQuark created_textures;
    GQuark reused_textures;
    GQuark surface_uploads;
  } counters;

  Fbo default_fbo;

  GHashTable *textures;         /* texture_id -> Texture */
  GHashTable *pointer_textures; /* pointer -> texture_id */

  const Texture *bound_source_texture;

  int max_texture_size;

  gboolean in_frame : 1;
};

G_DEFINE_TYPE (GskGLDriver, gsk_gl_driver, G_TYPE_OBJECT)

static void
upload_gdk_texture (GdkTexture      *source_texture,
                    int              target,
                    int              x_offset,
                    int              y_offset,
                    int              width,
                    int              height)
{
  cairo_surface_t *surface = NULL;
  GdkMemoryFormat data_format;
  const guchar *data;
  gsize data_stride;
  gsize bpp;

  g_return_if_fail (source_texture != NULL);
  g_return_if_fail (x_offset + width <= gdk_texture_get_width (source_texture));
  g_return_if_fail (y_offset + height <= gdk_texture_get_height (source_texture));

  /* Note: GdkGLTextures are already handled before we reach this and reused as-is */

  if (GDK_IS_MEMORY_TEXTURE (source_texture))
    {
      GdkMemoryTexture *memory_texture = GDK_MEMORY_TEXTURE (source_texture);
      data = gdk_memory_texture_get_data (memory_texture);
      data_format = gdk_memory_texture_get_format (memory_texture);
      data_stride = gdk_memory_texture_get_stride (memory_texture);
    }
  else
    {
      /* Fall back to downloading to a surface */
      surface = gdk_texture_download_surface (source_texture);
      cairo_surface_flush (surface);
      data = cairo_image_surface_get_data (surface);
      data_format = GDK_MEMORY_DEFAULT;
      data_stride = cairo_image_surface_get_stride (surface);
    }

  bpp = gdk_memory_format_bytes_per_pixel (data_format);

  gdk_gl_context_upload_texture (gdk_gl_context_get_current (),
                                 data + x_offset * bpp + y_offset * data_stride,
                                 width, height, data_stride,
                                 data_format, target);

  if (surface)
    cairo_surface_destroy (surface);
}

static Texture *
texture_new (void)
{
  return g_slice_new0 (Texture);
}

static inline void
fbo_clear (const Fbo *f)
{
  if (f->depth_stencil_id != 0)
    glDeleteRenderbuffers (1, &f->depth_stencil_id);

  glDeleteFramebuffers (1, &f->fbo_id);
}

static void
texture_free (gpointer data)
{
  Texture *t = data;
  guint i;

  if (t->user)
    gdk_texture_clear_render_data (t->user);

  if (t->fbo.fbo_id != 0)
    fbo_clear (&t->fbo);

  if (t->texture_id != 0)
    {
      glDeleteTextures (1, &t->texture_id);
    }
  else
    {
      g_assert_cmpint (t->n_slices, >, 0);

      for (i = 0; i < t->n_slices; i ++)
        glDeleteTextures (1, &t->slices[i].texture_id);
    }

  g_slice_free (Texture, t);
}

static void
gsk_gl_driver_set_texture_parameters (GskGLDriver *self,
                                      int          min_filter,
                                      int          mag_filter)
{
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);

  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}

static void
gsk_gl_driver_finalize (GObject *gobject)
{
  GskGLDriver *self = GSK_GL_DRIVER (gobject);

  gdk_gl_context_make_current (self->gl_context);

  g_clear_pointer (&self->textures, g_hash_table_unref);
  g_clear_pointer (&self->pointer_textures, g_hash_table_unref);
  g_clear_object (&self->profiler);

  if (self->gl_context == gdk_gl_context_get_current ())
    gdk_gl_context_clear_current ();

  G_OBJECT_CLASS (gsk_gl_driver_parent_class)->finalize (gobject);
}

static void
gsk_gl_driver_class_init (GskGLDriverClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->finalize = gsk_gl_driver_finalize;
}

static void
gsk_gl_driver_init (GskGLDriver *self)
{
  self->textures = g_hash_table_new_full (NULL, NULL, NULL, texture_free);

  self->max_texture_size = -1;

#ifdef G_ENABLE_DEBUG
  self->profiler = gsk_profiler_new ();
  self->counters.created_textures = gsk_profiler_add_counter (self->profiler,
                                                              "created_textures",
                                                              "Textures created this frame",
                                                              TRUE);
  self->counters.reused_textures = gsk_profiler_add_counter (self->profiler,
                                                             "reused_textures",
                                                             "Textures reused this frame",
                                                             TRUE);
  self->counters.surface_uploads = gsk_profiler_add_counter (self->profiler,
                                                             "surface_uploads",
                                                             "Texture uploads from surfaces this frame",
                                                             TRUE);
#endif
}

GskGLDriver *
gsk_gl_driver_new (GdkGLContext *context)
{
  GskGLDriver *self;
  g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);

  self = (GskGLDriver *) g_object_new (GSK_TYPE_GL_DRIVER, NULL);
  self->gl_context = context;

  return self;
}

void
gsk_gl_driver_begin_frame (GskGLDriver *self)
{
  g_return_if_fail (GSK_IS_GL_DRIVER (self));
  g_return_if_fail (!self->in_frame);

  self->in_frame = TRUE;

  if (self->max_texture_size < 0)
    {
      glGetIntegerv (GL_MAX_TEXTURE_SIZE, (GLint *) &self->max_texture_size);
      GSK_NOTE (OPENGL, g_message ("GL max texture size: %d", self->max_texture_size));
    }

  glBindFramebuffer (GL_FRAMEBUFFER, 0);

  glActiveTexture (GL_TEXTURE0);
  glBindTexture (GL_TEXTURE_2D, 0);

  glActiveTexture (GL_TEXTURE0 + 1);
  glBindTexture (GL_TEXTURE_2D, 0);

  glBindVertexArray (0);
  glUseProgram (0);

  glActiveTexture (GL_TEXTURE0);

#ifdef G_ENABLE_DEBUG
  gsk_profiler_reset (self->profiler);
#endif
}

gboolean
gsk_gl_driver_in_frame (GskGLDriver *self)
{
  return self->in_frame;
}

void
gsk_gl_driver_end_frame (GskGLDriver *self)
{
  g_return_if_fail (GSK_IS_GL_DRIVER (self));
  g_return_if_fail (self->in_frame);

  self->bound_source_texture = NULL;

  self->default_fbo.fbo_id = 0;

#ifdef G_ENABLE_DEBUG
  GSK_NOTE (OPENGL,
            g_message ("Textures created: %" G_GINT64_FORMAT "\n"
                     " Textures reused: %" G_GINT64_FORMAT "\n"
                     " Surface uploads: %" G_GINT64_FORMAT,
                     gsk_profiler_counter_get (self->profiler, self->counters.created_textures),
                     gsk_profiler_counter_get (self->profiler, self->counters.reused_textures),
                     gsk_profiler_counter_get (self->profiler, self->counters.surface_uploads)));
#endif

  GSK_NOTE (OPENGL,
            g_message ("*** Frame end: textures=%d",
                     g_hash_table_size (self->textures)));

  self->in_frame = FALSE;
}

int
gsk_gl_driver_collect_textures (GskGLDriver *self)
{
  GHashTableIter iter;
  gpointer value_p = NULL;
  int old_size;

  g_return_val_if_fail (GSK_IS_GL_DRIVER (self), 0);
  g_return_val_if_fail (!self->in_frame, 0);

  old_size = g_hash_table_size (self->textures);

  g_hash_table_iter_init (&iter, self->textures);
  while (g_hash_table_iter_next (&iter, NULL, &value_p))
    {
      Texture *t = value_p;

      if (t->user || t->permanent)
        continue;

      if (t->in_use)
        {
          t->in_use = FALSE;

          if (t->fbo.fbo_id != 0)
            {
              fbo_clear (&t->fbo);
              t->fbo.fbo_id = 0;
            }
        }
      else
        {
          /* Remove from self->pointer_textures. */
          /* TODO: Is there a better way for this? */
          if (self->pointer_textures)
            {
              GHashTableIter pointer_iter;
              gpointer value;
              gpointer p;

              g_hash_table_iter_init (&pointer_iter, self->pointer_textures);
              while (g_hash_table_iter_next (&pointer_iter, &p, &value))
                {
                  if (GPOINTER_TO_INT (value) == t->texture_id)
                    {
                      g_hash_table_iter_remove (&pointer_iter);
                      break;
                    }
                }
            }

          g_hash_table_iter_remove (&iter);
        }
    }

  return old_size - g_hash_table_size (self->textures);
}


GdkGLContext *
gsk_gl_driver_get_gl_context (GskGLDriver *self)
{
  return self->gl_context;
}

int
gsk_gl_driver_get_max_texture_size (GskGLDriver *self)
{
  if (self->max_texture_size < 0)
    {
      if (gdk_gl_context_get_use_es (self->gl_context))
        return 2048;

      return 1024;
    }

  return self->max_texture_size;
}

static Texture *
gsk_gl_driver_get_texture (GskGLDriver *self,
                           int          texture_id)
{
  Texture *t;

  if (g_hash_table_lookup_extended (self->textures, GINT_TO_POINTER (texture_id), NULL, (gpointer *) &t))
    return t;

  return NULL;
}

static Texture *
create_texture (GskGLDriver *self,
                float        fwidth,
                float        fheight)
{
  guint texture_id;
  Texture *t;
  int width = ceilf (fwidth);
  int height = ceilf (fheight);

  g_assert (width > 0);
  g_assert (height > 0);

  if (width > self->max_texture_size ||
      height > self->max_texture_size)
    {
      g_critical ("Texture %d x %d is bigger than supported texture limit of %d; clipping...",
                  width, height,
                  self->max_texture_size);

      width = MIN (width, self->max_texture_size);
      height = MIN (height, self->max_texture_size);
    }

  glGenTextures (1, &texture_id);
  t = texture_new ();
  t->texture_id = texture_id;
  t->width = width;
  t->height = height;
  t->min_filter = GL_NEAREST;
  t->mag_filter = GL_NEAREST;
  t->in_use = TRUE;
  g_hash_table_insert (self->textures, GINT_TO_POINTER (texture_id), t);
#ifdef G_ENABLE_DEBUG
  gsk_profiler_counter_inc (self->profiler, self->counters.created_textures);
#endif

  return t;
}

static void
gsk_gl_driver_release_texture (gpointer data)
{
  Texture *t = data;

  t->user = NULL;
}

void
gsk_gl_driver_slice_texture (GskGLDriver   *self,
                             GdkTexture    *texture,
                             TextureSlice **out_slices,
                             guint         *out_n_slices)
{
  const int max_texture_size = gsk_gl_driver_get_max_texture_size (self) / 4; // XXX Too much?
  const int tex_width = texture->width;
  const int tex_height = texture->height;
  const int cols = (texture->width / max_texture_size) + 1;
  const int rows = (texture->height / max_texture_size) + 1;
  int col, row;
  int x = 0, y = 0; /* Position in the texture */
  TextureSlice *slices;
  Texture *tex;

  g_assert (tex_width > max_texture_size || tex_height > max_texture_size);


  tex = gdk_texture_get_render_data (texture, self);

  if (tex != NULL)
    {
      g_assert (tex->n_slices > 0);
      *out_slices = tex->slices;
      *out_n_slices = tex->n_slices;
      return;
    }

  slices = g_new0 (TextureSlice, cols * rows);

  for (col = 0; col < cols; col ++)
    {
      const int slice_width = MIN (max_texture_size, texture->width - x);

      for (row = 0; row < rows; row ++)
        {
          const int slice_height = MIN (max_texture_size, texture->height - y);
          const int slice_index = (col * rows) + row;
          guint texture_id;

          glGenTextures (1, &texture_id);

#ifdef G_ENABLE_DEBUG
          gsk_profiler_counter_inc (self->profiler, self->counters.created_textures);
#endif
          glBindTexture (GL_TEXTURE_2D, texture_id);
          gsk_gl_driver_set_texture_parameters (self, GL_NEAREST, GL_NEAREST);
          upload_gdk_texture (texture, GL_TEXTURE_2D, x, y, slice_width, slice_height);

#ifdef G_ENABLE_DEBUG
          gsk_profiler_counter_inc (self->profiler, self->counters.surface_uploads);
#endif

          slices[slice_index].rect = (GdkRectangle){x, y, slice_width, slice_height};
          slices[slice_index].texture_id = texture_id;

          y += slice_height;
        }

      y = 0;
      x += slice_width;
    }

  /* Allocate one Texture for the entire thing. */
  tex = texture_new ();
  tex->width = texture->width;
  tex->height = texture->height;
  tex->min_filter = GL_NEAREST;
  tex->mag_filter = GL_NEAREST;
  tex->in_use = TRUE;
  tex->slices = slices;
  tex->n_slices = cols * rows;

  /* Use texture_free as destroy notify here since we are not inserting this Texture
   * into self->textures! */
  gdk_texture_set_render_data (texture, self, tex, texture_free);

  *out_slices = slices;
  *out_n_slices = cols * rows;
}

int
gsk_gl_driver_get_texture_for_texture (GskGLDriver *self,
                                       GdkTexture  *texture,
                                       int          min_filter,
                                       int          mag_filter)
{
  Texture *t;
  GdkTexture *downloaded_texture = NULL;
  GdkTexture *source_texture;

  if (GDK_IS_GL_TEXTURE (texture))
    {
      GdkGLContext *texture_context = gdk_gl_texture_get_context ((GdkGLTexture *)texture);
      GdkGLContext *shared_context = gdk_gl_context_get_shared_context (self->gl_context);

      if (texture_context == self->gl_context ||
          (gdk_gl_context_get_shared_context (texture_context) == shared_context && shared_context != NULL))
        {
          /* A GL texture from the same GL context is a simple task... */
          return gdk_gl_texture_get_id ((GdkGLTexture *)texture);
        }
      else
        {
          cairo_surface_t *surface;

          /* In this case, we have to temporarily make the texture's context the current one,
           * download its data into our context and then create a texture from it. */
          if (texture_context)
            gdk_gl_context_make_current (texture_context);

          surface = gdk_texture_download_surface (texture);
          downloaded_texture = gdk_texture_new_for_surface (surface);
          cairo_surface_destroy (surface);

          gdk_gl_context_make_current (self->gl_context);

          source_texture = downloaded_texture;
        }
    }
  else
    {
      t = gdk_texture_get_render_data (texture, self);

      if (t)
        {
          if (t->min_filter == min_filter && t->mag_filter == mag_filter)
            return t->texture_id;
        }

      source_texture = texture;
    }

  t = create_texture (self, gdk_texture_get_width (texture), gdk_texture_get_height (texture));

  if (gdk_texture_set_render_data (texture, self, t, gsk_gl_driver_release_texture))
    t->user = texture;

  gsk_gl_driver_bind_source_texture (self, t->texture_id);
  gsk_gl_driver_init_texture (self,
                              t->texture_id,
                              source_texture,
                              min_filter,
                              mag_filter);
  gdk_gl_context_label_object_printf (self->gl_context, GL_TEXTURE, t->texture_id,
                                      "GdkTexture<%p> %d", texture, t->texture_id);

  if (downloaded_texture)
    g_object_unref (downloaded_texture);

  return t->texture_id;
}

static guint
texture_key_hash (gconstpointer v)
{
  const GskTextureKey *k = (GskTextureKey *)v;

  return GPOINTER_TO_UINT (k->pointer)
         + (guint)(k->scale_x * 100)
         + (guint)(k->scale_y * 100)
         + (guint)k->filter * 2 +
         + (guint)k->pointer_is_child;
}

static gboolean
texture_key_equal (gconstpointer v1, gconstpointer v2)
{
  const GskTextureKey *k1 = (GskTextureKey *)v1;
  const GskTextureKey *k2 = (GskTextureKey *)v2;

  return k1->pointer == k2->pointer &&
         k1->scale_x == k2->scale_x &&
         k1->scale_y == k2->scale_y &&
         k1->filter == k2->filter &&
         k1->pointer_is_child == k2->pointer_is_child &&
         (!k1->pointer_is_child || graphene_rect_equal (&k1->parent_rect, &k2->parent_rect));
}

int
gsk_gl_driver_get_texture_for_key (GskGLDriver   *self,
                                   GskTextureKey *key)
{
  int id = 0;

  if (G_UNLIKELY (self->pointer_textures == NULL))
    self->pointer_textures = g_hash_table_new_full (texture_key_hash, texture_key_equal, g_free, NULL);

  id = GPOINTER_TO_INT (g_hash_table_lookup (self->pointer_textures, key));

  if (id != 0)
    {
      Texture *t;

      t = g_hash_table_lookup (self->textures, GINT_TO_POINTER (id));

      if (t != NULL)
        t->in_use = TRUE;
    }

  return id;
}

void
gsk_gl_driver_set_texture_for_key (GskGLDriver   *self,
                                   GskTextureKey *key,
                                   int            texture_id)
{
  GskTextureKey *k;

  if (G_UNLIKELY (self->pointer_textures == NULL))
    self->pointer_textures = g_hash_table_new_full (texture_key_hash, texture_key_equal, g_free, NULL);

  k = g_new (GskTextureKey, 1);
  *k = *key;

  g_hash_table_insert (self->pointer_textures, k, GINT_TO_POINTER (texture_id));
}

int
gsk_gl_driver_create_texture (GskGLDriver *self,
                              float        width,
                              float        height)
{
  Texture *t;

  g_return_val_if_fail (GSK_IS_GL_DRIVER (self), -1);

  t = create_texture (self, width, height);

  return t->texture_id;
}

void
gsk_gl_driver_create_render_target (GskGLDriver *self,
                                    int          width,
                                    int          height,
                                    int          min_filter,
                                    int          mag_filter,
                                    int         *out_texture_id,
                                    int         *out_render_target_id)
{
  GLuint fbo_id;
  Texture *texture;

  g_return_if_fail (self->in_frame);

  texture = create_texture (self, width, height);
  gsk_gl_driver_bind_source_texture (self, texture->texture_id);
  gsk_gl_driver_init_texture_empty (self, texture->texture_id, min_filter, mag_filter);

  glGenFramebuffers (1, &fbo_id);
  glBindFramebuffer (GL_FRAMEBUFFER, fbo_id);
  glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->texture_id, 0);

#if 0
  if (add_depth_buffer || add_stencil_buffer)
    {
      glGenRenderbuffersEXT (1, &depth_stencil_buffer_id);
      gdk_gl_context_label_object_printf (self->gl_context, GL_RENDERBUFFER, depth_stencil_buffer_id,
                                          "%s buffer for %d", add_depth_buffer ? "Depth" : "Stencil", texture_id);
    }
  else
    depth_stencil_buffer_id = 0;

  glBindRenderbuffer (GL_RENDERBUFFER, depth_stencil_buffer_id);

  if (add_depth_buffer || add_stencil_buffer)
    {
      if (add_stencil_buffer)
        glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, t->width, t->height);
      else
        glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, t->width, t->height);

      if (add_depth_buffer)
        glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
                                   GL_RENDERBUFFER, depth_stencil_buffer_id);

      if (add_stencil_buffer)
        glFramebufferRenderbufferEXT (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
                                      GL_RENDERBUFFER, depth_stencil_buffer_id);
  texture->fbo.depth_stencil_id = depth_stencil_buffer_id;
    }
#endif

  texture->fbo.fbo_id = fbo_id;

  g_assert_cmphex (glCheckFramebufferStatus (GL_FRAMEBUFFER), ==, GL_FRAMEBUFFER_COMPLETE);

  glBindFramebuffer (GL_FRAMEBUFFER, self->default_fbo.fbo_id);

  *out_texture_id = texture->texture_id;
  *out_render_target_id = fbo_id;
}

/* Mark the texture permanent, meaning it won'e be reused by the GLDriver.
 * E.g. to store it in some other cache. */
void
gsk_gl_driver_mark_texture_permanent (GskGLDriver *self,
                                      int          texture_id)
{
  Texture *t = gsk_gl_driver_get_texture (self, texture_id);

  g_assert (t != NULL);

  t->permanent = TRUE;
}

void
gsk_gl_driver_bind_source_texture (GskGLDriver *self,
                                   int          texture_id)
{
  Texture *t;

  g_return_if_fail (GSK_IS_GL_DRIVER (self));
  g_return_if_fail (self->in_frame);

  t = gsk_gl_driver_get_texture (self, texture_id);
  if (t == NULL)
    {
      g_critical ("No texture %d found.", texture_id);
      return;
    }

  if (self->bound_source_texture != t)
    {
      glActiveTexture (GL_TEXTURE0);
      glBindTexture (GL_TEXTURE_2D, t->texture_id);

      self->bound_source_texture = t;
    }
}

void
gsk_gl_driver_destroy_texture (GskGLDriver *self,
                               int          texture_id)
{
  g_return_if_fail (GSK_IS_GL_DRIVER (self));

  g_hash_table_remove (self->textures, GINT_TO_POINTER (texture_id));
}


void
gsk_gl_driver_init_texture_empty (GskGLDriver *self,
                                  int          texture_id,
                                  int          min_filter,
                                  int          mag_filter)
{
  Texture *t;

  g_return_if_fail (GSK_IS_GL_DRIVER (self));

  t = gsk_gl_driver_get_texture (self, texture_id);
  if (t == NULL)
    {
      g_critical ("No texture %d found.", texture_id);
      return;
    }

  if (self->bound_source_texture != t)
    {
      g_critical ("You must bind the texture before initializing it.");
      return;
    }

  t->min_filter = min_filter;
  t->mag_filter = mag_filter;

  gsk_gl_driver_set_texture_parameters (self, t->min_filter, t->mag_filter);

  if (gdk_gl_context_get_use_es (self->gl_context))
    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, t->width, t->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
  else
    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, t->width, t->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);

  glBindTexture (GL_TEXTURE_2D, 0);
}

static gboolean
filter_uses_mipmaps (int filter)
{
  return filter != GL_NEAREST && filter != GL_LINEAR;
}

void
gsk_gl_driver_init_texture (GskGLDriver     *self,
                            int              texture_id,
                            GdkTexture      *texture,
                            int              min_filter,
                            int              mag_filter)
{
  Texture *t;

  g_return_if_fail (GSK_IS_GL_DRIVER (self));

  t = gsk_gl_driver_get_texture (self, texture_id);
  if (t == NULL)
    {
      g_critical ("No texture %d found.", texture_id);
      return;
    }

  if (self->bound_source_texture != t)
    {
      g_critical ("You must bind the texture before initializing it.");
      return;
    }

  gsk_gl_driver_set_texture_parameters (self, min_filter, mag_filter);

  upload_gdk_texture (texture, GL_TEXTURE_2D, 0, 0, t->width, t->height);

#ifdef G_ENABLE_DEBUG
  gsk_profiler_counter_inc (self->profiler, self->counters.surface_uploads);
#endif

  t->min_filter = min_filter;
  t->mag_filter = mag_filter;

  if (filter_uses_mipmaps (t->min_filter))
    glGenerateMipmap (GL_TEXTURE_2D);
}
