gl renderer: Support large textures
authorTimm Bäder <mail@baedert.org>
Tue, 20 Mar 2018 08:17:26 +0000 (09:17 +0100)
committerTimm Bäder <mail@baedert.org>
Tue, 20 Mar 2018 08:40:10 +0000 (09:40 +0100)
By tiling them.

gsk/gl/gskgldriver.c
gsk/gl/gskgldriverprivate.h
gsk/gl/gskglrenderer.c

index c97522b812a0c376965f8a5462a507f0784a2d49..3f6c4c70d0c571630e4a849c8187f60ba0843d01 100644 (file)
@@ -25,6 +25,10 @@ typedef struct {
   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
@@ -72,6 +76,7 @@ static void
 texture_free (gpointer data)
 {
   Texture *t = data;
+  guint i;
 
   if (t->user)
     gdk_texture_clear_render_data (t->user);
@@ -79,10 +84,32 @@ texture_free (gpointer data)
   if (t->fbo.fbo_id != 0)
     fbo_clear (&t->fbo);
 
-  glDeleteTextures (1, &t->texture_id);
+  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)
@@ -313,8 +340,8 @@ create_texture (GskGLDriver *self,
   g_assert (width > 0);
   g_assert (height > 0);
 
-  if (width >= self->max_texture_size ||
-      height >= self->max_texture_size)
+  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,
@@ -362,6 +389,108 @@ gsk_gl_driver_release_texture (gpointer 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);
+
+  /* TODO: (Perf):
+   *   We still create a surface here, which should obviously be unnecessary
+   *   and we should eventually remove it and upload the data directly.
+   */
+  for (col = 0; col < cols; col ++)
+    {
+      const int slice_width = MIN (max_texture_size, texture->width - x);
+      const int stride = slice_width * 4;
+
+      for (row = 0; row < rows; row ++)
+        {
+          const int slice_height = MIN (max_texture_size, texture->height - y);
+          const int slice_index = (col * rows) + row;
+          guchar *data;
+          guint texture_id;
+          cairo_surface_t *surface;
+
+          data = g_malloc (sizeof (guchar) * stride * slice_height);
+
+          gdk_texture_download_area (texture,
+                                     &(GdkRectangle){x, y, slice_width, slice_height},
+                                     data, stride);
+          surface = cairo_image_surface_create_for_data (data,
+                                                         CAIRO_FORMAT_ARGB32,
+                                                         slice_width, slice_height,
+                                                         stride);
+
+          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);
+          gdk_cairo_surface_upload_to_gl (surface, GL_TEXTURE_2D, slice_width, slice_height, NULL);
+
+#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;
+
+          g_free (data);
+          cairo_surface_destroy (surface);
+
+          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,
@@ -386,7 +515,7 @@ gsk_gl_driver_get_texture_for_texture (GskGLDriver *self,
       else
         {
           /* A GL texture from the same GL context is a simple task... */
-          return gdk_gl_texture_get_id (GDK_GL_TEXTURE (texture));
+          return gdk_gl_texture_get_id ((GdkGLTexture *)texture);
         }
     }
   else
@@ -573,17 +702,6 @@ gsk_gl_driver_destroy_texture (GskGLDriver *self,
   g_hash_table_remove (self->textures, GINT_TO_POINTER (texture_id));
 }
 
-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);
-}
 
 void
 gsk_gl_driver_init_texture_empty (GskGLDriver *self,
@@ -654,14 +772,3 @@ gsk_gl_driver_init_texture_with_surface (GskGLDriver     *self,
   if (t->min_filter != GL_NEAREST)
     glGenerateMipmap (GL_TEXTURE_2D);
 }
-
-gboolean
-gsk_gl_driver_texture_needs_tiling (GskGLDriver *self,
-                                    GdkTexture  *texture)
-{
-  int max = gsk_gl_driver_get_max_texture_size (self);
-
-  g_assert (self->max_texture_size > -1);
-
-  return texture->width > max || texture->height > max;
-}
index 04f97a0661b5a7b8ad2ce15956a056bd4959cc58..7d7a41fe28229088be6a5fea5c1e2ab74e77a285 100644 (file)
@@ -16,6 +16,12 @@ typedef struct {
   float uv[2];
 } GskQuadVertex;
 
+typedef struct {
+  cairo_rectangle_int_t rect;
+  guint texture_id;
+} TextureSlice;
+
+
 GskGLDriver *   gsk_gl_driver_new                       (GdkGLContext    *context);
 
 int             gsk_gl_driver_get_max_texture_size      (GskGLDriver     *driver);
@@ -33,8 +39,6 @@ int             gsk_gl_driver_create_permanent_texture  (GskGLDriver     *driver
 int             gsk_gl_driver_create_texture            (GskGLDriver     *driver,
                                                          float            width,
                                                          float            height);
-gboolean        gsk_gl_driver_texture_needs_tiling      (GskGLDriver     *driver,
-                                                         GdkTexture      *texture);
 int             gsk_gl_driver_create_render_target      (GskGLDriver     *driver,
                                                          int              texture_id,
                                                          gboolean         add_depth_buffer,
@@ -57,6 +61,10 @@ void            gsk_gl_driver_destroy_texture           (GskGLDriver     *driver
                                                          int              texture_id);
 
 int             gsk_gl_driver_collect_textures          (GskGLDriver     *driver);
+void            gsk_gl_driver_slice_texture             (GskGLDriver     *self,
+                                                         GdkTexture      *texture,
+                                                         TextureSlice   **out_slices,
+                                                         guint           *out_n_slices);
 
 G_END_DECLS
 
index f41ec1a64c5f2681e0c9544a2cafdf45ef4190d2..127f50e0f115adf66433801c65926e8fd2a83392 100644 (file)
@@ -556,17 +556,47 @@ render_texture_node (GskGLRenderer       *self,
                      RenderOpBuilder     *builder)
 {
   GdkTexture *texture = gsk_texture_node_get_texture (node);
+  const int max_texture_size = gsk_gl_driver_get_max_texture_size (self->gl_driver);
+  const float min_x = builder->dx + node->bounds.origin.x;
+  const float min_y = builder->dy + node->bounds.origin.y;
+  const float max_x = min_x + node->bounds.size.width;
+  const float max_y = min_y + node->bounds.size.height;
 
-  if (gsk_gl_driver_texture_needs_tiling (self->gl_driver, texture))
+  if (texture->width > max_texture_size || texture->height > max_texture_size)
     {
+      const float scale_x = (max_x - min_x) / texture->width;
+      const float scale_y = (max_y - min_y) / texture->height;
+      TextureSlice *slices;
+      guint n_slices;
+      guint i;
 
+      gsk_gl_driver_slice_texture (self->gl_driver, texture, &slices, &n_slices);
+
+      ops_set_program (builder, &self->blit_program);
+      for (i = 0; i < n_slices; i ++)
+        {
+          const TextureSlice *slice = &slices[i];
+          float x1, x2, y1, y2;
+
+          x1 = min_x + (scale_x * slice->rect.x);
+          x2 = x1 + (slice->rect.width * scale_x);
+          y1 = min_y + (scale_y * slice->rect.y);
+          y2 = y1 + (slice->rect.height * scale_y);
+
+          ops_set_texture (builder, slice->texture_id);
+          ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
+            { { x1, y1 }, { 0, 0 }, },
+            { { x1, y2 }, { 0, 1 }, },
+            { { x2, y1 }, { 1, 0 }, },
+
+            { { x2, y2 }, { 1, 1 }, },
+            { { x1, y2 }, { 0, 1 }, },
+            { { x2, y1 }, { 1, 0 }, },
+          });
+        }
     }
   else
     {
-      const float min_x = builder->dx + node->bounds.origin.x;
-      const float min_y = builder->dy + node->bounds.origin.y;
-      const float max_x = min_x + node->bounds.size.width;
-      const float max_y = min_y + node->bounds.size.height;
       int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST;
       int texture_id;