1 
2 #include "config.h"
3 #include "gskgltextureatlasprivate.h"
4 #include "gskdebugprivate.h"
5 #include "gdkglcontextprivate.h"
6 #include <epoxy/gl.h>
7 
8 #define ATLAS_SIZE (512)
9 #define MAX_OLD_RATIO 0.5
10 
11 static void
free_atlas(gpointer v)12 free_atlas (gpointer v)
13 {
14   GskGLTextureAtlas *atlas = v;
15 
16   gsk_gl_texture_atlas_free (atlas);
17 
18   g_free (atlas);
19 }
20 
21 GskGLTextureAtlases *
gsk_gl_texture_atlases_new(void)22 gsk_gl_texture_atlases_new (void)
23 {
24   GskGLTextureAtlases *self;
25 
26   self = g_new (GskGLTextureAtlases, 1);
27   self->atlases = g_ptr_array_new_with_free_func (free_atlas);
28 
29   self->ref_count = 1;
30 
31   return self;
32 }
33 
34 GskGLTextureAtlases *
gsk_gl_texture_atlases_ref(GskGLTextureAtlases * self)35 gsk_gl_texture_atlases_ref (GskGLTextureAtlases *self)
36 {
37   self->ref_count++;
38 
39   return self;
40 }
41 
42 void
gsk_gl_texture_atlases_unref(GskGLTextureAtlases * self)43 gsk_gl_texture_atlases_unref (GskGLTextureAtlases *self)
44 {
45   g_assert (self->ref_count > 0);
46 
47   if (self->ref_count == 1)
48     {
49       g_ptr_array_unref (self->atlases);
50       g_free (self);
51       return;
52     }
53 
54   self->ref_count--;
55 }
56 
57 #if 0
58 static void
59 write_atlas_to_png (GskGLTextureAtlas *atlas,
60                     const char        *filename)
61 {
62   int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, atlas->width);
63   guchar *data = g_malloc (atlas->height * stride);
64   cairo_surface_t *s;
65 
66   glBindTexture (GL_TEXTURE_2D, atlas->texture_id);
67   glGetTexImage (GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
68   s = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, atlas->width, atlas->height, stride);
69   cairo_surface_write_to_png (s, filename);
70 
71   cairo_surface_destroy (s);
72   g_free (data);
73 }
74 #endif
75 
76 void
gsk_gl_texture_atlases_begin_frame(GskGLTextureAtlases * self,GPtrArray * removed)77 gsk_gl_texture_atlases_begin_frame (GskGLTextureAtlases *self,
78                                     GPtrArray           *removed)
79 {
80   int i;
81 
82   for (i = self->atlases->len - 1; i >= 0; i--)
83     {
84       GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
85 
86       if (gsk_gl_texture_atlas_get_unused_ratio (atlas) > MAX_OLD_RATIO)
87         {
88           GSK_NOTE(GLYPH_CACHE,
89                    g_message ("Dropping atlas %d (%g.2%% old)", i,
90                               100.0 * gsk_gl_texture_atlas_get_unused_ratio (atlas)));
91 
92           if (atlas->texture_id != 0)
93             {
94               glDeleteTextures (1, &atlas->texture_id);
95               atlas->texture_id = 0;
96             }
97 
98           g_ptr_array_add (removed, atlas);
99           g_ptr_array_remove_index (self->atlases, i);
100        }
101     }
102 
103   GSK_NOTE(GLYPH_CACHE, {
104     static guint timestamp;
105     if (timestamp++ % 60 == 0)
106       g_message ("%d atlases", self->atlases->len);
107   });
108 
109 
110 #if 0
111   {
112     static guint timestamp;
113 
114     timestamp++;
115     if (timestamp % 10 == 0)
116       for (i = 0; i < self->atlases->len; i++)
117         {
118           GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
119 
120           if (atlas->texture_id)
121             {
122               char *filename;
123 
124               filename = g_strdup_printf ("textureatlas%d-%u.png", i, timestamp);
125               write_atlas_to_png (atlas, filename);
126               g_free (filename);
127             }
128          }
129    }
130 #endif
131 }
132 
133 gboolean
gsk_gl_texture_atlases_pack(GskGLTextureAtlases * self,int width,int height,GskGLTextureAtlas ** atlas_out,int * out_x,int * out_y)134 gsk_gl_texture_atlases_pack (GskGLTextureAtlases *self,
135                              int                  width,
136                              int                  height,
137                              GskGLTextureAtlas  **atlas_out,
138                              int                 *out_x,
139                              int                 *out_y)
140 {
141   GskGLTextureAtlas *atlas;
142   int x, y;
143   int i;
144 
145   g_assert (width  < ATLAS_SIZE);
146   g_assert (height < ATLAS_SIZE);
147 
148   atlas = NULL;
149 
150   for (i = 0; i < self->atlases->len; i++)
151     {
152       atlas = g_ptr_array_index (self->atlases, i);
153 
154       if (gsk_gl_texture_atlas_pack (atlas, width, height, &x, &y))
155         break;
156 
157       atlas = NULL;
158     }
159 
160   if (atlas == NULL)
161     {
162       /* No atlas has enough space, so create a new one... */
163       atlas = g_malloc (sizeof (GskGLTextureAtlas));
164       gsk_gl_texture_atlas_init (atlas, ATLAS_SIZE, ATLAS_SIZE);
165       gsk_gl_texture_atlas_realize (atlas);
166       g_ptr_array_add (self->atlases, atlas);
167 
168       /* Pack it onto that one, which surely has enough space... */
169       if (!gsk_gl_texture_atlas_pack (atlas, width, height, &x, &y))
170         g_assert_not_reached ();
171 
172       GSK_NOTE(GLYPH_CACHE, g_message ("adding new atlas"));
173     }
174 
175   *atlas_out = atlas;
176   *out_x = x;
177   *out_y = y;
178 
179   return TRUE;
180 }
181 
182 void
gsk_gl_texture_atlas_init(GskGLTextureAtlas * self,int width,int height)183 gsk_gl_texture_atlas_init (GskGLTextureAtlas *self,
184                            int                width,
185                            int                height)
186 {
187   memset (self, 0, sizeof (*self));
188 
189   self->texture_id = 0;
190   self->width = width;
191   self->height = height;
192 
193   /* TODO: We might want to change the strategy about the amount of
194    *       nodes here? stb_rect_pack.h says with is optimal. */
195   self->nodes = g_malloc0 (sizeof (struct stbrp_node) * width);
196   stbrp_init_target (&self->context,
197                      width, height,
198                      self->nodes,
199                      width);
200 
201   gsk_gl_texture_atlas_realize (self);
202 }
203 
204 void
gsk_gl_texture_atlas_free(GskGLTextureAtlas * self)205 gsk_gl_texture_atlas_free (GskGLTextureAtlas *self)
206 {
207   if (self->texture_id != 0)
208     {
209       glDeleteTextures (1, &self->texture_id);
210       self->texture_id = 0;
211     }
212 
213   g_clear_pointer (&self->nodes, g_free);
214 }
215 
216 void
gsk_gl_texture_atlas_mark_unused(GskGLTextureAtlas * self,int width,int height)217 gsk_gl_texture_atlas_mark_unused (GskGLTextureAtlas *self,
218                                   int                width,
219                                   int                height)
220 {
221   self->unused_pixels += (width * height);
222 }
223 
224 
225 void
gsk_gl_texture_atlas_mark_used(GskGLTextureAtlas * self,int width,int height)226 gsk_gl_texture_atlas_mark_used (GskGLTextureAtlas *self,
227                                 int                width,
228                                 int                height)
229 {
230   self->unused_pixels -= (width * height);
231 
232   g_assert (self->unused_pixels >= 0);
233 }
234 
235 gboolean
gsk_gl_texture_atlas_pack(GskGLTextureAtlas * self,int width,int height,int * out_x,int * out_y)236 gsk_gl_texture_atlas_pack (GskGLTextureAtlas *self,
237                            int                width,
238                            int                height,
239                            int               *out_x,
240                            int               *out_y)
241 {
242   stbrp_rect rect;
243 
244   g_assert (out_x);
245   g_assert (out_y);
246 
247   rect.w = width;
248   rect.h = height;
249 
250   stbrp_pack_rects (&self->context, &rect, 1);
251 
252   if (rect.was_packed)
253     {
254       *out_x = rect.x;
255       *out_y = rect.y;
256     }
257 
258   return rect.was_packed;
259 }
260 
261 double
gsk_gl_texture_atlas_get_unused_ratio(const GskGLTextureAtlas * self)262 gsk_gl_texture_atlas_get_unused_ratio (const GskGLTextureAtlas *self)
263 {
264   if (self->unused_pixels > 0)
265     return (double)(self->unused_pixels) / (double)(self->width * self->height);
266 
267   return 0.0;
268 }
269 
270 /* Not using gdk_gl_driver_create_texture here, since we want
271  * this texture to survive the driver and stay around until
272  * the display gets closed.
273  */
274 static guint
create_shared_texture(int width,int height)275 create_shared_texture (int width,
276                        int height)
277 {
278   guint texture_id;
279 
280   glGenTextures (1, &texture_id);
281   glBindTexture (GL_TEXTURE_2D, texture_id);
282 
283   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
284   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
285 
286   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
287   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
288 
289   if (gdk_gl_context_get_use_es (gdk_gl_context_get_current ()))
290     glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
291   else
292     glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
293 
294   glBindTexture (GL_TEXTURE_2D, 0);
295 
296   return texture_id;
297 }
298 
299 void
gsk_gl_texture_atlas_realize(GskGLTextureAtlas * atlas)300 gsk_gl_texture_atlas_realize (GskGLTextureAtlas *atlas)
301 {
302   if (atlas->texture_id)
303     return;
304 
305   atlas->texture_id = create_shared_texture (atlas->width, atlas->height);
306   gdk_gl_context_label_object_printf (gdk_gl_context_get_current (),
307                                       GL_TEXTURE, atlas->texture_id,
308                                       "Texture atlas %d", atlas->texture_id);
309 }
310