• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2     Copyright (C) 2009-2010 Samsung Electronics
3     Copyright (C) 2009-2010 ProFUSION embedded systems
4 
5     This library is free software; you can redistribute it and/or
6     modify it under the terms of the GNU Library General Public
7     License as published by the Free Software Foundation; either
8     version 2 of the License, or (at your option) any later version.
9 
10     This library is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13     Library General Public License for more details.
14 
15     You should have received a copy of the GNU Library General Public License
16     along with this library; see the file COPYING.LIB.  If not, write to
17     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18     Boston, MA 02110-1301, USA.
19 */
20 
21 #include "config.h"
22 #include "ewk_tiled_model.h"
23 
24 #define _GNU_SOURCE
25 #include "ewk_tiled_backing_store.h"
26 #include "ewk_tiled_private.h"
27 #include <Eina.h>
28 #include <eina_safety_checks.h>
29 #include <errno.h>
30 #include <inttypes.h>
31 #include <stdio.h> // XXX REMOVE ME LATER
32 #include <stdlib.h>
33 #include <string.h>
34 
35 #ifdef TILE_STATS_ACCOUNT_RENDER_TIME
36 #include <sys/time.h>
37 #endif
38 
39 #ifndef CAIRO_FORMAT_RGB16_565
40 #define CAIRO_FORMAT_RGB16_565 4
41 #endif
42 
43 #define IDX(col, row, rowspan) (col + (row * rowspan))
44 #define MIN(a, b) ((a < b) ? a : b)
45 #define MAX(a, b) ((a > b) ? a : b)
46 
47 #ifdef DEBUG_MEM_LEAKS
48 static uint64_t tiles_allocated = 0;
49 static uint64_t tiles_freed = 0;
50 static uint64_t bytes_allocated = 0;
51 static uint64_t bytes_freed = 0;
52 
53 struct tile_account {
54     Evas_Coord size;
55     struct {
56         uint64_t allocated;
57         uint64_t freed;
58     } tiles, bytes;
59 };
60 
61 static size_t accounting_len = 0;
62 static struct tile_account *accounting = NULL;
63 
_ewk_tile_account_get(const Ewk_Tile * t)64 static inline struct tile_account *_ewk_tile_account_get(const Ewk_Tile *t)
65 {
66     struct tile_account *acc;
67     size_t i;
68 
69     for (i = 0; i < accounting_len; i++) {
70         if (accounting[i].size == t->w)
71             return accounting + i;
72     }
73 
74     i = (accounting_len + 1) * sizeof(struct tile_account);
75     REALLOC_OR_OOM_RET(accounting, i, NULL);
76 
77     acc = accounting + accounting_len;
78     acc->size = t->w;
79     acc->tiles.allocated = 0;
80     acc->tiles.freed = 0;
81     acc->bytes.allocated = 0;
82     acc->bytes.freed = 0;
83 
84     accounting_len++;
85 
86     return acc;
87 }
88 
_ewk_tile_account_allocated(const Ewk_Tile * t)89 static inline void _ewk_tile_account_allocated(const Ewk_Tile *t)
90 {
91     struct tile_account *acc = _ewk_tile_account_get(t);
92     if (!acc)
93         return;
94     acc->bytes.allocated += t->bytes;
95     acc->tiles.allocated++;
96 
97     bytes_allocated += t->bytes;
98     tiles_allocated++;
99 }
100 
_ewk_tile_account_freed(const Ewk_Tile * t)101 static inline void _ewk_tile_account_freed(const Ewk_Tile *t)
102 {
103     struct tile_account *acc = _ewk_tile_account_get(t);
104     if (!acc)
105         return;
106 
107     acc->bytes.freed += t->bytes;
108     acc->tiles.freed++;
109 
110     bytes_freed += t->bytes;
111     tiles_freed++;
112 }
113 
ewk_tile_accounting_dbg(void)114 void ewk_tile_accounting_dbg(void)
115 {
116     struct tile_account *acc;
117     struct tile_account *acc_end;
118 
119     printf("TILE BALANCE: tiles[+%"PRIu64",-%"PRIu64":%"PRIu64"] "
120            "bytes[+%"PRIu64",-%"PRIu64":%"PRIu64"]\n",
121             tiles_allocated, tiles_freed, tiles_allocated - tiles_freed,
122             bytes_allocated, bytes_freed, bytes_allocated - bytes_freed);
123 
124     if (!accounting_len)
125         return;
126 
127     acc = accounting;
128     acc_end = acc + accounting_len;
129     printf("BEGIN: TILE BALANCE DETAILS (TO THIS MOMENT!):\n");
130     for (; acc < acc_end; acc++) {
131         uint64_t tiles, bytes;
132 
133         tiles = acc->tiles.allocated - acc->tiles.freed;
134         bytes = acc->bytes.allocated - acc->bytes.freed;
135 
136         printf("   %4d: tiles[+%4"PRIu64",-%4"PRIu64":%4"PRIu64"] "
137                "bytes[+%8"PRIu64",-%8"PRIu64":%8"PRIu64"]%s\n",
138                acc->size,
139                acc->tiles.allocated, acc->tiles.freed, tiles,
140                acc->bytes.allocated, acc->bytes.freed, bytes,
141                (bytes || tiles) ? " POSSIBLE LEAK" : "");
142     }
143     printf("END: TILE BALANCE DETAILS (TO THIS MOMENT!):\n");
144 }
145 #else
146 
_ewk_tile_account_allocated(const Ewk_Tile * t)147 static inline void _ewk_tile_account_allocated(const Ewk_Tile *t) { }
_ewk_tile_account_freed(const Ewk_Tile * t)148 static inline void _ewk_tile_account_freed(const Ewk_Tile *t) { }
149 
ewk_tile_accounting_dbg(void)150 void ewk_tile_accounting_dbg(void)
151 {
152     printf("compile webkit with DEBUG_MEM_LEAKS defined!\n");
153 }
154 #endif
155 
_ewk_tile_paint_rgb888(Ewk_Tile * t,uint8_t r,uint8_t g,uint8_t b)156 static inline void _ewk_tile_paint_rgb888(Ewk_Tile *t, uint8_t r, uint8_t g, uint8_t b)
157 {
158     uint32_t *dst32, *dst32_end, c1;
159     uint64_t *dst64, *dst64_end, c2;
160 
161     c1 = 0xff000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
162     c2 = ((uint64_t)c1 << 32) | c1;
163 
164     dst64 = (uint64_t *)t->pixels;
165     dst64_end = dst64 + ((t->bytes / 8) & ~7);
166     for (; dst64 < dst64_end; dst64 += 8) {
167         /* TODO: ARM add pld or NEON instructions */
168         dst64[0] = c2;
169         dst64[1] = c2;
170         dst64[2] = c2;
171         dst64[3] = c2;
172         dst64[4] = c2;
173         dst64[5] = c2;
174         dst64[6] = c2;
175         dst64[7] = c2;
176     }
177 
178     dst32 = (uint32_t *)dst64_end;
179     dst32_end = (uint32_t *)(t->pixels + t->bytes);
180     for (; dst32 < dst32_end; dst32++)
181         *dst32 = c1;
182 }
183 
_ewk_tile_paint_rgb565(Ewk_Tile * t,uint8_t r,uint8_t g,uint8_t b)184 static inline void _ewk_tile_paint_rgb565(Ewk_Tile *t, uint8_t r, uint8_t g, uint8_t b)
185 {
186     uint16_t *dst16, *dst16_end, c1;
187     uint64_t *dst64, *dst64_end, c2;
188 
189     c1 = ((((r >> 3) & 0x1f) << 11) |
190           (((g >> 2) & 0x3f) << 5) |
191           ((b >> 3) & 0x1f));
192 
193     c2 = (((uint64_t)c1 << 48) | ((uint64_t)c1 << 32) |
194           ((uint64_t)c1 << 16) | c1);
195 
196     dst64 = (uint64_t *)t->pixels;
197     dst64_end = dst64 + ((t->bytes / 8) & ~7);
198     for (; dst64 < dst64_end; dst64 += 8) {
199         /* TODO: ARM add pld or NEON instructions */
200         dst64[0] = c2;
201         dst64[1] = c2;
202         dst64[2] = c2;
203         dst64[3] = c2;
204         dst64[4] = c2;
205         dst64[5] = c2;
206         dst64[6] = c2;
207         dst64[7] = c2;
208     }
209 
210     dst16 = (uint16_t *)dst16_end;
211     dst16_end = (uint16_t *)(t->pixels + t->bytes);
212     for (; dst16 < dst16_end; dst16++)
213         *dst16 = c1;
214 }
215 
_ewk_tile_paint(Ewk_Tile * t,uint8_t r,uint8_t g,uint8_t b)216 static inline void _ewk_tile_paint(Ewk_Tile *t, uint8_t r, uint8_t g, uint8_t b)
217 {
218     if (t->cspace == EVAS_COLORSPACE_ARGB8888)
219         _ewk_tile_paint_rgb888(t, r, g, b);
220     else if (t->cspace == EVAS_COLORSPACE_RGB565_A5P)
221         _ewk_tile_paint_rgb565(t, r, g, b);
222     else
223         ERR("unknown color space: %d", t->cspace);
224 }
225 
226 /**
227  * Create a new tile of given size, zoom level and colorspace.
228  *
229  * After created these properties are immutable as they're the basic
230  * characteristic of the tile and any change will lead to invalid
231  * memory access.
232  *
233  * Other members are of free-access and no getters/setters are
234  * provided in orderr to avoid expensive operations on those, however
235  * some are better manipulated with provided functions, such as
236  * ewk_tile_show() and ewk_tile_hide() to change
237  * @c visible or ewk_tile_update_full(), ewk_tile_update_area(),
238  * ewk_tile_updates_clear() to change @c stats.misses,
239  * @c stats.full_update and @c updates.
240  */
ewk_tile_new(Evas * evas,Evas_Coord w,Evas_Coord h,float zoom,Evas_Colorspace cspace)241 Ewk_Tile *ewk_tile_new(Evas *evas, Evas_Coord w, Evas_Coord h, float zoom, Evas_Colorspace cspace)
242 {
243     Eina_Inlist *l;
244     Evas_Coord *ec;
245     Evas_Colorspace *ecs;
246     float *f;
247     size_t *s;
248     Ewk_Tile *t;
249     unsigned int area;
250     size_t bytes;
251     cairo_format_t format;
252     cairo_status_t status;
253     int stride;
254 
255     area = w * h;
256 
257     if (cspace == EVAS_COLORSPACE_ARGB8888) {
258         bytes = area * 4;
259         stride = w * 4;
260         format = CAIRO_FORMAT_RGB24;
261     } else if (cspace == EVAS_COLORSPACE_RGB565_A5P) {
262         bytes = area * 2;
263         stride = w * 2;
264         format = CAIRO_FORMAT_RGB16_565;
265     } else {
266         ERR("unknown color space: %d", cspace);
267         return NULL;
268     }
269 
270     DBG("size: %dx%d (%d), zoom: %f, cspace=%d",
271         w, h, area, (double)zoom, cspace);
272 
273     MALLOC_OR_OOM_RET(t, sizeof(Ewk_Tile), NULL);
274     t->image = evas_object_image_add(evas);
275 
276     l = EINA_INLIST_GET(t);
277     l->prev = NULL;
278     l->next = NULL;
279 
280     t->visible = 0;
281     t->updates = NULL;
282 
283     memset(&t->stats, 0, sizeof(Ewk_Tile_Stats));
284     t->stats.area = area;
285 
286     /* ugly, but let's avoid at all costs having users to modify those */
287     ec = (Evas_Coord *)&t->w;
288     *ec = w;
289 
290     ec = (Evas_Coord *)&t->h;
291     *ec = h;
292 
293     ecs = (Evas_Colorspace *)&t->cspace;
294     *ecs = cspace;
295 
296     f = (float *)&t->zoom;
297     *f = zoom;
298 
299     s = (size_t *)&t->bytes;
300     *s = bytes;
301 
302     evas_object_image_size_set(t->image, t->w, t->h);
303     evas_object_image_colorspace_set(t->image, t->cspace);
304     t->pixels = evas_object_image_data_get(t->image, EINA_TRUE);
305     t->surface = cairo_image_surface_create_for_data
306         (t->pixels, format, w, h, stride);
307     status = cairo_surface_status(t->surface);
308     if (status != CAIRO_STATUS_SUCCESS) {
309         ERR("failed to create cairo surface: %s",
310             cairo_status_to_string(status));
311         free(t);
312         return NULL;
313     }
314 
315     t->cairo = cairo_create(t->surface);
316     status = cairo_status(t->cairo);
317     if (status != CAIRO_STATUS_SUCCESS) {
318         ERR("failed to create cairo: %s", cairo_status_to_string(status));
319         cairo_surface_destroy(t->surface);
320         evas_object_del(t->image);
321         free(t);
322         return NULL;
323     }
324 
325     _ewk_tile_account_allocated(t);
326 
327     return t;
328 }
329 
330 /**
331  * Free tile memory.
332  */
ewk_tile_free(Ewk_Tile * t)333 void ewk_tile_free(Ewk_Tile *t)
334 {
335     _ewk_tile_account_freed(t);
336 
337     if (t->updates)
338         eina_tiler_free(t->updates);
339 
340     cairo_surface_destroy(t->surface);
341     cairo_destroy(t->cairo);
342     evas_object_del(t->image);
343     free(t);
344 }
345 
346 /**
347  * Make the tile visible, incrementing its counter.
348  */
ewk_tile_show(Ewk_Tile * t)349 void ewk_tile_show(Ewk_Tile *t)
350 {
351     t->visible++;
352     evas_object_show(t->image);
353 }
354 
355 /**
356  * Decrement the visibility counter, making it invisible if necessary.
357  */
ewk_tile_hide(Ewk_Tile * t)358 void ewk_tile_hide(Ewk_Tile *t)
359 {
360     t->visible--;
361     if (!t->visible)
362         evas_object_hide(t->image);
363 }
364 
365 /**
366  * Returns EINA_TRUE if the tile is visible, EINA_FALSE otherwise.
367  */
ewk_tile_visible_get(Ewk_Tile * t)368 Eina_Bool ewk_tile_visible_get(Ewk_Tile *t)
369 {
370     return !!t->visible;
371 }
372 
373 /**
374  * Mark whole tile as dirty and requiring update.
375  */
ewk_tile_update_full(Ewk_Tile * t)376 void ewk_tile_update_full(Ewk_Tile *t)
377 {
378     /* TODO: list of tiles pending updates? */
379     t->stats.misses++;
380 
381     if (!t->stats.full_update) {
382         t->stats.full_update = EINA_TRUE;
383         if (t->updates) {
384             eina_tiler_free(t->updates);
385             t->updates = NULL;
386         }
387     }
388 }
389 
390 /**
391  * Mark the specific subarea as dirty and requiring update.
392  */
ewk_tile_update_area(Ewk_Tile * t,const Eina_Rectangle * r)393 void ewk_tile_update_area(Ewk_Tile *t, const Eina_Rectangle *r)
394 {
395     /* TODO: list of tiles pending updates? */
396     t->stats.misses++;
397 
398     if (t->stats.full_update)
399         return;
400 
401     if (!r->x && !r->y && r->w == t->w && r->h == t->h) {
402         t->stats.full_update = EINA_TRUE;
403         if (t->updates) {
404             eina_tiler_free(t->updates);
405             t->updates = NULL;
406         }
407         return;
408     }
409 
410     if (!t->updates) {
411         t->updates = eina_tiler_new(t->w, t->h);
412         if (!t->updates) {
413             CRITICAL("could not create eina_tiler %dx%d.", t->w, t->h);
414             return;
415         }
416     }
417 
418     eina_tiler_rect_add(t->updates, r);
419 }
420 
421 /**
422  * For each updated region, call the given function.
423  *
424  * This will not change the tile statistics or clear the processed
425  * updates, use ewk_tile_updates_clear() for that.
426  */
ewk_tile_updates_process(Ewk_Tile * t,void (* cb)(void * data,Ewk_Tile * t,const Eina_Rectangle * update),const void * data)427 void ewk_tile_updates_process(Ewk_Tile *t, void (*cb)(void *data, Ewk_Tile *t, const Eina_Rectangle *update), const void *data)
428 {
429     if (t->stats.full_update) {
430         Eina_Rectangle r;
431         r.x = 0;
432         r.y = 0;
433         r.w = t->w;
434         r.h = t->h;
435 #ifdef TILE_STATS_ACCOUNT_RENDER_TIME
436         struct timeval timev;
437         double render_start;
438         gettimeofday(&timev, NULL);
439         render_start = (double)timev.tv_sec +
440             (((double)timev.tv_usec) / 1000000);
441 #endif
442         cb((void *)data, t, &r);
443 #ifdef TILE_STATS_ACCOUNT_RENDER_TIME
444         gettimeofday(&timev, NULL);
445         t->stats.render_time = (double)timev.tv_sec +
446             (((double)timev.tv_usec) / 1000000) - render_start;
447 #endif
448     } else if (t->updates) {
449         Eina_Iterator *itr = eina_tiler_iterator_new(t->updates);
450         Eina_Rectangle *r;
451         if (!itr) {
452             CRITICAL("could not create tiler iterator!");
453             return;
454         }
455         EINA_ITERATOR_FOREACH(itr, r)
456             cb((void *)data, t, r);
457         eina_iterator_free(itr);
458     }
459 }
460 
461 /**
462  * Clear all updates in region, if any.
463  *
464  * This will change the tile statistics, specially zero stat.misses
465  * and unset stats.full_update. If t->updates existed, then it will be
466  * destroyed.
467  *
468  * This function is usually called after ewk_tile_updates_process() is
469  * called.
470  */
ewk_tile_updates_clear(Ewk_Tile * t)471 void ewk_tile_updates_clear(Ewk_Tile *t)
472 {
473     /* TODO: remove from list of pending updates? */
474     t->stats.misses = 0;
475 
476     if (t->stats.full_update)
477         t->stats.full_update = 0;
478     else if (t->updates) {
479         eina_tiler_free(t->updates);
480         t->updates = NULL;
481     }
482 }
483 
484 typedef struct _Ewk_Tile_Unused_Cache_Entry Ewk_Tile_Unused_Cache_Entry;
485 struct _Ewk_Tile_Unused_Cache_Entry {
486     Ewk_Tile *tile;
487     int weight;
488     struct {
489         void (*cb)(void *data, Ewk_Tile *t);
490         void *data;
491     } tile_free;
492 };
493 
494 struct _Ewk_Tile_Unused_Cache {
495     struct {
496         Eina_List *list;
497         size_t count;
498         size_t allocated;
499     } entries;
500     struct {
501         size_t max;  /**< watermark (in bytes) to start freeing tiles */
502         size_t used; /**< in bytes, maybe more than max. */
503     } memory;
504     struct {
505         Evas_Coord x, y, w, h;
506         float zoom;
507         Eina_Bool locked;
508     } locked;
509     int references;
510     unsigned int frozen;
511     Eina_Bool dirty:1;
512 };
513 
514 static const size_t TILE_UNUSED_CACHE_ALLOCATE_INITIAL = 128;
515 static const size_t TILE_UNUSED_CACHE_ALLOCATE_STEP = 16;
516 static const size_t TILE_UNUSED_CACHE_MAX_FREE = 32;
517 
518 /**
519  * Cache of unused tiles (those that are not visible).
520  *
521  * The cache of unused tiles.
522  *
523  * @param max cache size in bytes.
524  *
525  * @return newly allocated cache of unused tiles, use
526  *         ewk_tile_unused_cache_free() to release resources. If not
527  *         possible to allocate memory, @c NULL is returned.
528  */
ewk_tile_unused_cache_new(size_t max)529 Ewk_Tile_Unused_Cache *ewk_tile_unused_cache_new(size_t max)
530 {
531     Ewk_Tile_Unused_Cache *tuc;
532 
533     CALLOC_OR_OOM_RET(tuc, sizeof(Ewk_Tile_Unused_Cache), NULL);
534 
535     DBG("tuc=%p", tuc);
536     tuc->memory.max = max;
537     tuc->references = 1;
538     return tuc;
539 }
540 
ewk_tile_unused_cache_lock_area(Ewk_Tile_Unused_Cache * tuc,Evas_Coord x,Evas_Coord y,Evas_Coord w,Evas_Coord h,float zoom)541 void ewk_tile_unused_cache_lock_area(Ewk_Tile_Unused_Cache *tuc, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h, float zoom)
542 {
543     EINA_SAFETY_ON_NULL_RETURN(tuc);
544 
545     tuc->locked.locked = EINA_TRUE;
546     tuc->locked.x = x;
547     tuc->locked.y = y;
548     tuc->locked.w = w;
549     tuc->locked.h = h;
550     tuc->locked.zoom = zoom;
551 }
552 
ewk_tile_unused_cache_unlock_area(Ewk_Tile_Unused_Cache * tuc)553 void ewk_tile_unused_cache_unlock_area(Ewk_Tile_Unused_Cache *tuc)
554 {
555     EINA_SAFETY_ON_NULL_RETURN(tuc);
556 
557     tuc->locked.locked = EINA_FALSE;
558 }
559 
560 /**
561  * Free cache of unused tiles.
562  *
563  * Those tiles that are still visible will remain live. The unused
564  * tiles will be freed.
565  *
566  * @see ewk_tile_unused_cache_unref()
567  */
ewk_tile_unused_cache_free(Ewk_Tile_Unused_Cache * tuc)568 void ewk_tile_unused_cache_free(Ewk_Tile_Unused_Cache *tuc)
569 {
570     EINA_SAFETY_ON_NULL_RETURN(tuc);
571 
572     DBG("tuc=%p, "
573         "entries=(count:%zd, allocated:%zd), "
574         "memory=(max:%zd, used:%zd)",
575         tuc, tuc->entries.count, tuc->entries.allocated,
576         tuc->memory.max, tuc->memory.used);
577 
578     ewk_tile_unused_cache_clear(tuc);
579     free(tuc);
580 }
581 
582 /**
583  * Clear cache of unused tiles.
584  *
585  * Any tiles that are in the cache are freed. The only tiles that are
586  * kept are those that aren't in the cache (i.e. that are visible).
587  */
ewk_tile_unused_cache_clear(Ewk_Tile_Unused_Cache * tuc)588 void ewk_tile_unused_cache_clear(Ewk_Tile_Unused_Cache *tuc)
589 {
590     Ewk_Tile_Unused_Cache_Entry *itr;
591     EINA_SAFETY_ON_NULL_RETURN(tuc);
592 
593     if (!tuc->entries.count)
594         return;
595 
596     EINA_LIST_FREE(tuc->entries.list, itr) {
597         itr->tile_free.cb(itr->tile_free.data, itr->tile);
598         free(itr);
599     }
600 
601     tuc->memory.used = 0;
602     tuc->entries.count = 0;
603     tuc->dirty = EINA_FALSE;
604 }
605 
606 /**
607  * Hold reference to cache.
608  *
609  * @return same pointer as taken.
610  *
611  * @see ewk_tile_unused_cache_unref()
612  */
ewk_tile_unused_cache_ref(Ewk_Tile_Unused_Cache * tuc)613 Ewk_Tile_Unused_Cache *ewk_tile_unused_cache_ref(Ewk_Tile_Unused_Cache *tuc)
614 {
615     EINA_SAFETY_ON_NULL_RETURN_VAL(tuc, NULL);
616     tuc->references++;
617     return tuc;
618 }
619 
620 /**
621  * Release cache reference, freeing it if it drops to zero.
622  *
623  * @see ewk_tile_unused_cache_ref()
624  * @see ewk_tile_unused_cache_free()
625  */
ewk_tile_unused_cache_unref(Ewk_Tile_Unused_Cache * tuc)626 void ewk_tile_unused_cache_unref(Ewk_Tile_Unused_Cache *tuc)
627 {
628     EINA_SAFETY_ON_NULL_RETURN(tuc);
629     tuc->references--;
630     if (!tuc->references)
631         ewk_tile_unused_cache_free(tuc);
632 }
633 
634 /**
635  * Change cache capacity, in bytes.
636  *
637  * This will not flush cache, use ewk_tile_unused_cache_flush() or
638  * ewk_tile_unused_cache_auto_flush() to do so.
639  */
ewk_tile_unused_cache_max_set(Ewk_Tile_Unused_Cache * tuc,size_t max)640 void ewk_tile_unused_cache_max_set(Ewk_Tile_Unused_Cache *tuc, size_t max)
641 {
642     EINA_SAFETY_ON_NULL_RETURN(tuc);
643     tuc->memory.max = max;
644 }
645 
646 /**
647  * Retrieve maximum cache capacity, in bytes.
648  */
ewk_tile_unused_cache_max_get(const Ewk_Tile_Unused_Cache * tuc)649 size_t ewk_tile_unused_cache_max_get(const Ewk_Tile_Unused_Cache *tuc)
650 {
651     EINA_SAFETY_ON_NULL_RETURN_VAL(tuc, 0);
652     return tuc->memory.max;
653 }
654 
655 /**
656  * Retrieve the used cache capacity, in bytes.
657  */
ewk_tile_unused_cache_used_get(const Ewk_Tile_Unused_Cache * tuc)658 size_t ewk_tile_unused_cache_used_get(const Ewk_Tile_Unused_Cache *tuc)
659 {
660     EINA_SAFETY_ON_NULL_RETURN_VAL(tuc, 0);
661     return tuc->memory.used;
662 }
663 
664 /**
665  * Flush given amount of bytes from cache.
666  *
667  * After calling this function, near @a bytes are freed from cache. It
668  * may be less if cache did not contain that amount of bytes (ie: an
669  * empty cache has nothing to free!) or more if the cache just
670  * contained objects that were larger than the requested amount (this
671  * is usually the case).
672  *
673  * @param tuc cache of unused tiles.
674  * @param bytes amount to free.
675  *
676  * @return amount really freed.
677  *
678  * @see ewk_tile_unused_cache_used_get()
679  */
ewk_tile_unused_cache_flush(Ewk_Tile_Unused_Cache * tuc,size_t bytes)680 size_t ewk_tile_unused_cache_flush(Ewk_Tile_Unused_Cache *tuc, size_t bytes)
681 {
682     Ewk_Tile_Unused_Cache_Entry *itr;
683     Eina_List *l, *l_next;
684     EINA_SAFETY_ON_NULL_RETURN_VAL(tuc, 0);
685     size_t done;
686     unsigned int count;
687 
688     if (!tuc->entries.count)
689         return 0;
690     if (bytes < 1)
691         return 0;
692 
693     /*
694      * NOTE: the cache is a FIFO queue currently.
695      * Don't need to sort any more.
696      */
697 
698     if (tuc->dirty)
699         tuc->dirty = EINA_FALSE;
700 
701     done = 0;
702     count = 0;
703     EINA_LIST_FOREACH_SAFE(tuc->entries.list, l, l_next, itr) {
704         Ewk_Tile *t = itr->tile;
705         if (done > bytes)
706             break;
707         if (tuc->locked.locked
708             && t->x + t->w > tuc->locked.x
709             && t->y + t->h > tuc->locked.y
710             && t->x < tuc->locked.x + tuc->locked.w
711             && t->y < tuc->locked.y + tuc->locked.h
712             && t->zoom == tuc->locked.zoom) {
713             continue;
714         }
715         done += sizeof(Ewk_Tile) + itr->tile->bytes;
716         itr->tile_free.cb(itr->tile_free.data, itr->tile);
717         tuc->entries.list = eina_list_remove_list(tuc->entries.list, l);
718         free(itr);
719         count++;
720     }
721 
722     tuc->memory.used -= done;
723     tuc->entries.count -= count;
724 
725     return done;
726 }
727 
728 /**
729  * Flush enough bytes to make cache usage lower than maximum.
730  *
731  * Just like ewk_tile_unused_cache_flush(), but this will make the cache
732  * free enough tiles to respect maximum cache size as defined with
733  * ewk_tile_unused_cache_max_set().
734  *
735  * This function is usually called when system becomes idle. This way
736  * we keep memory low but do not impact performance when
737  * creating/deleting tiles.
738  */
ewk_tile_unused_cache_auto_flush(Ewk_Tile_Unused_Cache * tuc)739 void ewk_tile_unused_cache_auto_flush(Ewk_Tile_Unused_Cache *tuc)
740 {
741     EINA_SAFETY_ON_NULL_RETURN(tuc);
742     if (tuc->memory.used <= tuc->memory.max)
743         return;
744     ewk_tile_unused_cache_flush(tuc, tuc->memory.used - tuc->memory.max);
745     if (tuc->memory.used > tuc->memory.max)
746         CRITICAL("Cache still using too much memory: %zd KB; max: %zd KB",
747                  tuc->memory.used, tuc->memory.max);
748 }
749 
750 /**
751  * Flag cache as dirty.
752  *
753  * If cache is dirty then next flush operations will have to recompute
754  * weight and sort again to find the best tiles to expire.
755  *
756  * One must call this function when tile properties that may change
757  * likeness of tile to be flushed change, like Tile::stats.
758  */
ewk_tile_unused_cache_dirty(Ewk_Tile_Unused_Cache * tuc)759 void ewk_tile_unused_cache_dirty(Ewk_Tile_Unused_Cache *tuc)
760 {
761     tuc->dirty = EINA_TRUE;
762 }
763 
764 /**
765  * Freeze cache to not do maintenance tasks.
766  *
767  * Maintenance tasks optimize cache usage, but maybe we know we should
768  * hold on them until we do the last operation, in this case we freeze
769  * while operating and then thaw when we're done.
770  *
771  * @see ewk_tile_unused_cache_thaw()
772  */
ewk_tile_unused_cache_freeze(Ewk_Tile_Unused_Cache * tuc)773 void ewk_tile_unused_cache_freeze(Ewk_Tile_Unused_Cache *tuc)
774 {
775     tuc->frozen++;
776 }
777 
778 /**
779  * Unfreezes maintenance tasks.
780  *
781  * If this is the last counterpart of freeze, then maintenance tasks
782  * will run immediately.
783  */
ewk_tile_unused_cache_thaw(Ewk_Tile_Unused_Cache * tuc)784 void ewk_tile_unused_cache_thaw(Ewk_Tile_Unused_Cache *tuc)
785 {
786     if (!tuc->frozen) {
787         ERR("thawing more than freezing!");
788         return;
789     }
790 
791     tuc->frozen--;
792 }
793 
794 /**
795  * Get tile from cache of unused tiles, removing it from the cache.
796  *
797  * If the tile is used, then it's not in cache of unused tiles, so it
798  * is removed from the cache and may be given back with
799  * ewk_tile_unused_cache_tile_put().
800  *
801  * @param tuc cache of unused tiles
802  * @param t the tile to be removed from Ewk_Tile_Unused_Cache.
803  *
804  * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
805  */
ewk_tile_unused_cache_tile_get(Ewk_Tile_Unused_Cache * tuc,Ewk_Tile * t)806 Eina_Bool ewk_tile_unused_cache_tile_get(Ewk_Tile_Unused_Cache *tuc, Ewk_Tile *t)
807 {
808     Ewk_Tile_Unused_Cache_Entry *entry;
809     Eina_List *e, *l;
810 
811     e = NULL;
812     EINA_LIST_FOREACH(tuc->entries.list, l, entry)
813     {
814         if (entry->tile == t) {
815             e = l;
816             break;
817         }
818     }
819     if (!e) {
820         ERR("tile %p not found in cache %p", t, tuc);
821         return EINA_FALSE;
822     }
823 
824     tuc->entries.count--;
825     tuc->memory.used -= sizeof(Ewk_Tile) + t->bytes;
826     tuc->entries.list = eina_list_remove_list(tuc->entries.list, e);
827     free(entry);
828     // TODO assume dirty for now, but may it's not,
829     // if the item was at the beginning of the queue
830     tuc->dirty = EINA_TRUE;
831 
832     return EINA_TRUE;
833 }
834 
835 /**
836  * Put tile into cache of unused tiles, adding it to the cache.
837  *
838  * This should be called when @c t->visible is @c 0 and no objects are
839  * using the tile anymore, making it available to be expired and have
840  * its memory replaced.
841  *
842  * Note that tiles are not automatically deleted if cache is full,
843  * instead the cache will have more bytes used than maximum and one
844  * can call ewk_tile_unused_cache_auto_flush() to free them. This is done
845  * because usually we want a lazy operation for better performance.
846  *
847  * @param tuc cache of unused tiles
848  * @param t tile to be added to cache.
849  * @param tile_free_cb function used to free tiles.
850  * @param data context to give back to @a tile_free_cb as first argument.
851  *
852  * @return #EINA_TRUE on success, #EINA_FALSE otherwise. If @c t->visible
853  *         is not #EINA_FALSE, then it will return #EINA_FALSE.
854  *
855  * @see ewk_tile_unused_cache_auto_flush()
856  */
ewk_tile_unused_cache_tile_put(Ewk_Tile_Unused_Cache * tuc,Ewk_Tile * t,void (* tile_free_cb)(void * data,Ewk_Tile * t),const void * data)857 Eina_Bool ewk_tile_unused_cache_tile_put(Ewk_Tile_Unused_Cache *tuc, Ewk_Tile *t, void (*tile_free_cb)(void *data, Ewk_Tile *t), const void *data)
858 {
859     Ewk_Tile_Unused_Cache_Entry *e;
860 
861     if (t->visible) {
862         ERR("tile=%p is not unused (visible=%d)", t, t->visible);
863         return EINA_FALSE;
864     }
865 
866     MALLOC_OR_OOM_RET(e, sizeof(Ewk_Tile_Unused_Cache_Entry), EINA_FALSE);
867     tuc->entries.list = eina_list_append(tuc->entries.list, e);
868     if (eina_error_get()) {
869         ERR("List allocation failed");
870         return EINA_FALSE;
871     }
872 
873     e->tile = t;
874     e->weight = 0; /* calculated just before sort */
875     e->tile_free.cb = tile_free_cb;
876     e->tile_free.data = (void *)data;
877 
878     tuc->entries.count++;
879     tuc->memory.used += sizeof(Ewk_Tile) + t->bytes;
880     tuc->dirty = EINA_TRUE;
881 
882     return EINA_TRUE;
883 }
884 
ewk_tile_unused_cache_dbg(const Ewk_Tile_Unused_Cache * tuc)885 void ewk_tile_unused_cache_dbg(const Ewk_Tile_Unused_Cache *tuc)
886 {
887     Ewk_Tile_Unused_Cache_Entry *itr;
888     Eina_List *l;
889     int count = 0;
890     printf("Cache of unused tiles: entries: %zu/%zu, memory: %zu/%zu\n",
891            tuc->entries.count, tuc->entries.allocated,
892            tuc->memory.used, tuc->memory.max);
893 
894     EINA_LIST_FOREACH(tuc->entries.list, l, itr) {
895         const Ewk_Tile *t = itr->tile;
896         printf(" [%3lu,%3lu + %dx%d @ %0.3f]%c",
897                t->col, t->row, t->w, t->h, t->zoom,
898                t->visible ? '*': ' ');
899 
900         if (!(count % 4))
901             printf("\n");
902     }
903 
904     printf("\n");
905 }
906