summaryrefslogtreecommitdiffstats
path: root/Source/WebKit/efl/ewk/ewk_tiled_model.c
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebKit/efl/ewk/ewk_tiled_model.c')
-rw-r--r--Source/WebKit/efl/ewk/ewk_tiled_model.c905
1 files changed, 905 insertions, 0 deletions
diff --git a/Source/WebKit/efl/ewk/ewk_tiled_model.c b/Source/WebKit/efl/ewk/ewk_tiled_model.c
new file mode 100644
index 0000000..4b81a15
--- /dev/null
+++ b/Source/WebKit/efl/ewk/ewk_tiled_model.c
@@ -0,0 +1,905 @@
+/*
+ Copyright (C) 2009-2010 Samsung Electronics
+ Copyright (C) 2009-2010 ProFUSION embedded systems
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "config.h"
+#include "ewk_tiled_model.h"
+
+#define _GNU_SOURCE
+#include "ewk_tiled_backing_store.h"
+#include "ewk_tiled_private.h"
+#include <Eina.h>
+#include <eina_safety_checks.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h> // XXX REMOVE ME LATER
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef TILE_STATS_ACCOUNT_RENDER_TIME
+#include <sys/time.h>
+#endif
+
+#ifndef CAIRO_FORMAT_RGB16_565
+#define CAIRO_FORMAT_RGB16_565 4
+#endif
+
+#define IDX(col, row, rowspan) (col + (row * rowspan))
+#define MIN(a, b) ((a < b) ? a : b)
+#define MAX(a, b) ((a > b) ? a : b)
+
+#ifdef DEBUG_MEM_LEAKS
+static uint64_t tiles_allocated = 0;
+static uint64_t tiles_freed = 0;
+static uint64_t bytes_allocated = 0;
+static uint64_t bytes_freed = 0;
+
+struct tile_account {
+ Evas_Coord size;
+ struct {
+ uint64_t allocated;
+ uint64_t freed;
+ } tiles, bytes;
+};
+
+static size_t accounting_len = 0;
+static struct tile_account *accounting = NULL;
+
+static inline struct tile_account *_ewk_tile_account_get(const Ewk_Tile *t)
+{
+ struct tile_account *acc;
+ size_t i;
+
+ for (i = 0; i < accounting_len; i++) {
+ if (accounting[i].size == t->w)
+ return accounting + i;
+ }
+
+ i = (accounting_len + 1) * sizeof(struct tile_account);
+ REALLOC_OR_OOM_RET(accounting, i, NULL);
+
+ acc = accounting + accounting_len;
+ acc->size = t->w;
+ acc->tiles.allocated = 0;
+ acc->tiles.freed = 0;
+ acc->bytes.allocated = 0;
+ acc->bytes.freed = 0;
+
+ accounting_len++;
+
+ return acc;
+}
+
+static inline void _ewk_tile_account_allocated(const Ewk_Tile *t)
+{
+ struct tile_account *acc = _ewk_tile_account_get(t);
+ if (!acc)
+ return;
+ acc->bytes.allocated += t->bytes;
+ acc->tiles.allocated++;
+
+ bytes_allocated += t->bytes;
+ tiles_allocated++;
+}
+
+static inline void _ewk_tile_account_freed(const Ewk_Tile *t)
+{
+ struct tile_account *acc = _ewk_tile_account_get(t);
+ if (!acc)
+ return;
+
+ acc->bytes.freed += t->bytes;
+ acc->tiles.freed++;
+
+ bytes_freed += t->bytes;
+ tiles_freed++;
+}
+
+void ewk_tile_accounting_dbg(void)
+{
+ struct tile_account *acc;
+ struct tile_account *acc_end;
+
+ printf("TILE BALANCE: tiles[+%"PRIu64",-%"PRIu64":%"PRIu64"] "
+ "bytes[+%"PRIu64",-%"PRIu64":%"PRIu64"]\n",
+ tiles_allocated, tiles_freed, tiles_allocated - tiles_freed,
+ bytes_allocated, bytes_freed, bytes_allocated - bytes_freed);
+
+ if (!accounting_len)
+ return;
+
+ acc = accounting;
+ acc_end = acc + accounting_len;
+ printf("BEGIN: TILE BALANCE DETAILS (TO THIS MOMENT!):\n");
+ for (; acc < acc_end; acc++) {
+ uint64_t tiles, bytes;
+
+ tiles = acc->tiles.allocated - acc->tiles.freed;
+ bytes = acc->bytes.allocated - acc->bytes.freed;
+
+ printf(" %4d: tiles[+%4"PRIu64",-%4"PRIu64":%4"PRIu64"] "
+ "bytes[+%8"PRIu64",-%8"PRIu64":%8"PRIu64"]%s\n",
+ acc->size,
+ acc->tiles.allocated, acc->tiles.freed, tiles,
+ acc->bytes.allocated, acc->bytes.freed, bytes,
+ (bytes || tiles) ? " POSSIBLE LEAK" : "");
+ }
+ printf("END: TILE BALANCE DETAILS (TO THIS MOMENT!):\n");
+}
+#else
+
+static inline void _ewk_tile_account_allocated(const Ewk_Tile *t) { }
+static inline void _ewk_tile_account_freed(const Ewk_Tile *t) { }
+
+void ewk_tile_accounting_dbg(void)
+{
+ printf("compile webkit with DEBUG_MEM_LEAKS defined!\n");
+}
+#endif
+
+static inline void _ewk_tile_paint_rgb888(Ewk_Tile *t, uint8_t r, uint8_t g, uint8_t b)
+{
+ uint32_t *dst32, *dst32_end, c1;
+ uint64_t *dst64, *dst64_end, c2;
+
+ c1 = 0xff000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
+ c2 = ((uint64_t)c1 << 32) | c1;
+
+ dst64 = (uint64_t *)t->pixels;
+ dst64_end = dst64 + ((t->bytes / 8) & ~7);
+ for (; dst64 < dst64_end; dst64 += 8) {
+ /* TODO: ARM add pld or NEON instructions */
+ dst64[0] = c2;
+ dst64[1] = c2;
+ dst64[2] = c2;
+ dst64[3] = c2;
+ dst64[4] = c2;
+ dst64[5] = c2;
+ dst64[6] = c2;
+ dst64[7] = c2;
+ }
+
+ dst32 = (uint32_t *)dst64_end;
+ dst32_end = (uint32_t *)(t->pixels + t->bytes);
+ for (; dst32 < dst32_end; dst32++)
+ *dst32 = c1;
+}
+
+static inline void _ewk_tile_paint_rgb565(Ewk_Tile *t, uint8_t r, uint8_t g, uint8_t b)
+{
+ uint16_t *dst16, *dst16_end, c1;
+ uint64_t *dst64, *dst64_end, c2;
+
+ c1 = ((((r >> 3) & 0x1f) << 11) |
+ (((g >> 2) & 0x3f) << 5) |
+ ((b >> 3) & 0x1f));
+
+ c2 = (((uint64_t)c1 << 48) | ((uint64_t)c1 << 32) |
+ ((uint64_t)c1 << 16) | c1);
+
+ dst64 = (uint64_t *)t->pixels;
+ dst64_end = dst64 + ((t->bytes / 8) & ~7);
+ for (; dst64 < dst64_end; dst64 += 8) {
+ /* TODO: ARM add pld or NEON instructions */
+ dst64[0] = c2;
+ dst64[1] = c2;
+ dst64[2] = c2;
+ dst64[3] = c2;
+ dst64[4] = c2;
+ dst64[5] = c2;
+ dst64[6] = c2;
+ dst64[7] = c2;
+ }
+
+ dst16 = (uint16_t *)dst16_end;
+ dst16_end = (uint16_t *)(t->pixels + t->bytes);
+ for (; dst16 < dst16_end; dst16++)
+ *dst16 = c1;
+}
+
+static inline void _ewk_tile_paint(Ewk_Tile *t, uint8_t r, uint8_t g, uint8_t b)
+{
+ if (t->cspace == EVAS_COLORSPACE_ARGB8888)
+ _ewk_tile_paint_rgb888(t, r, g, b);
+ else if (t->cspace == EVAS_COLORSPACE_RGB565_A5P)
+ _ewk_tile_paint_rgb565(t, r, g, b);
+ else
+ ERR("unknown color space: %d", t->cspace);
+}
+
+/**
+ * Create a new tile of given size, zoom level and colorspace.
+ *
+ * After created these properties are immutable as they're the basic
+ * characteristic of the tile and any change will lead to invalid
+ * memory access.
+ *
+ * Other members are of free-access and no getters/setters are
+ * provided in orderr to avoid expensive operations on those, however
+ * some are better manipulated with provided functions, such as
+ * ewk_tile_show() and ewk_tile_hide() to change
+ * @c visible or ewk_tile_update_full(), ewk_tile_update_area(),
+ * ewk_tile_updates_clear() to change @c stats.misses,
+ * @c stats.full_update and @c updates.
+ */
+Ewk_Tile *ewk_tile_new(Evas *evas, Evas_Coord w, Evas_Coord h, float zoom, Evas_Colorspace cspace)
+{
+ Eina_Inlist *l;
+ Evas_Coord *ec;
+ Evas_Colorspace *ecs;
+ float *f;
+ size_t *s;
+ Ewk_Tile *t;
+ unsigned int area;
+ size_t bytes;
+ cairo_format_t format;
+ cairo_status_t status;
+ int stride;
+
+ area = w * h;
+
+ if (cspace == EVAS_COLORSPACE_ARGB8888) {
+ bytes = area * 4;
+ stride = w * 4;
+ format = CAIRO_FORMAT_RGB24;
+ } else if (cspace == EVAS_COLORSPACE_RGB565_A5P) {
+ bytes = area * 2;
+ stride = w * 2;
+ format = CAIRO_FORMAT_RGB16_565;
+ } else {
+ ERR("unknown color space: %d", cspace);
+ return NULL;
+ }
+
+ DBG("size: %dx%d (%d), zoom: %f, cspace=%d",
+ w, h, area, (double)zoom, cspace);
+
+ MALLOC_OR_OOM_RET(t, sizeof(Ewk_Tile), NULL);
+ t->image = evas_object_image_add(evas);
+
+ l = EINA_INLIST_GET(t);
+ l->prev = NULL;
+ l->next = NULL;
+
+ t->visible = 0;
+ t->updates = NULL;
+
+ memset(&t->stats, 0, sizeof(Ewk_Tile_Stats));
+ t->stats.area = area;
+
+ /* ugly, but let's avoid at all costs having users to modify those */
+ ec = (Evas_Coord *)&t->w;
+ *ec = w;
+
+ ec = (Evas_Coord *)&t->h;
+ *ec = h;
+
+ ecs = (Evas_Colorspace *)&t->cspace;
+ *ecs = cspace;
+
+ f = (float *)&t->zoom;
+ *f = zoom;
+
+ s = (size_t *)&t->bytes;
+ *s = bytes;
+
+ evas_object_image_size_set(t->image, t->w, t->h);
+ evas_object_image_colorspace_set(t->image, t->cspace);
+ t->pixels = evas_object_image_data_get(t->image, EINA_TRUE);
+ t->surface = cairo_image_surface_create_for_data
+ (t->pixels, format, w, h, stride);
+ status = cairo_surface_status(t->surface);
+ if (status != CAIRO_STATUS_SUCCESS) {
+ ERR("failed to create cairo surface: %s",
+ cairo_status_to_string(status));
+ free(t);
+ return NULL;
+ }
+
+ t->cairo = cairo_create(t->surface);
+ status = cairo_status(t->cairo);
+ if (status != CAIRO_STATUS_SUCCESS) {
+ ERR("failed to create cairo: %s", cairo_status_to_string(status));
+ cairo_surface_destroy(t->surface);
+ evas_object_del(t->image);
+ free(t);
+ return NULL;
+ }
+
+ _ewk_tile_account_allocated(t);
+
+ return t;
+}
+
+/**
+ * Free tile memory.
+ */
+void ewk_tile_free(Ewk_Tile *t)
+{
+ _ewk_tile_account_freed(t);
+
+ if (t->updates)
+ eina_tiler_free(t->updates);
+
+ cairo_surface_destroy(t->surface);
+ cairo_destroy(t->cairo);
+ evas_object_del(t->image);
+ free(t);
+}
+
+/**
+ * Make the tile visible, incrementing its counter.
+ */
+void ewk_tile_show(Ewk_Tile *t)
+{
+ t->visible++;
+ evas_object_show(t->image);
+}
+
+/**
+ * Decrement the visibility counter, making it invisible if necessary.
+ */
+void ewk_tile_hide(Ewk_Tile *t)
+{
+ t->visible--;
+ if (!t->visible)
+ evas_object_hide(t->image);
+}
+
+/**
+ * Returns EINA_TRUE if the tile is visible, EINA_FALSE otherwise.
+ */
+Eina_Bool ewk_tile_visible_get(Ewk_Tile *t)
+{
+ return !!t->visible;
+}
+
+/**
+ * Mark whole tile as dirty and requiring update.
+ */
+void ewk_tile_update_full(Ewk_Tile *t)
+{
+ /* TODO: list of tiles pending updates? */
+ t->stats.misses++;
+
+ if (!t->stats.full_update) {
+ t->stats.full_update = EINA_TRUE;
+ if (t->updates) {
+ eina_tiler_free(t->updates);
+ t->updates = NULL;
+ }
+ }
+}
+
+/**
+ * Mark the specific subarea as dirty and requiring update.
+ */
+void ewk_tile_update_area(Ewk_Tile *t, const Eina_Rectangle *r)
+{
+ /* TODO: list of tiles pending updates? */
+ t->stats.misses++;
+
+ if (t->stats.full_update)
+ return;
+
+ if (!r->x && !r->y && r->w == t->w && r->h == t->h) {
+ t->stats.full_update = EINA_TRUE;
+ if (t->updates) {
+ eina_tiler_free(t->updates);
+ t->updates = NULL;
+ }
+ return;
+ }
+
+ if (!t->updates) {
+ t->updates = eina_tiler_new(t->w, t->h);
+ if (!t->updates) {
+ CRITICAL("could not create eina_tiler %dx%d.", t->w, t->h);
+ return;
+ }
+ }
+
+ eina_tiler_rect_add(t->updates, r);
+}
+
+/**
+ * For each updated region, call the given function.
+ *
+ * This will not change the tile statistics or clear the processed
+ * updates, use ewk_tile_updates_clear() for that.
+ */
+void ewk_tile_updates_process(Ewk_Tile *t, void (*cb)(void *data, Ewk_Tile *t, const Eina_Rectangle *update), const void *data)
+{
+ if (t->stats.full_update) {
+ Eina_Rectangle r;
+ r.x = 0;
+ r.y = 0;
+ r.w = t->w;
+ r.h = t->h;
+#ifdef TILE_STATS_ACCOUNT_RENDER_TIME
+ struct timeval timev;
+ double render_start;
+ gettimeofday(&timev, NULL);
+ render_start = (double)timev.tv_sec +
+ (((double)timev.tv_usec) / 1000000);
+#endif
+ cb((void *)data, t, &r);
+#ifdef TILE_STATS_ACCOUNT_RENDER_TIME
+ gettimeofday(&timev, NULL);
+ t->stats.render_time = (double)timev.tv_sec +
+ (((double)timev.tv_usec) / 1000000) - render_start;
+#endif
+ } else if (t->updates) {
+ Eina_Iterator *itr = eina_tiler_iterator_new(t->updates);
+ Eina_Rectangle *r;
+ if (!itr) {
+ CRITICAL("could not create tiler iterator!");
+ return;
+ }
+ EINA_ITERATOR_FOREACH(itr, r)
+ cb((void *)data, t, r);
+ eina_iterator_free(itr);
+ }
+}
+
+/**
+ * Clear all updates in region, if any.
+ *
+ * This will change the tile statistics, specially zero stat.misses
+ * and unset stats.full_update. If t->updates existed, then it will be
+ * destroyed.
+ *
+ * This function is usually called after ewk_tile_updates_process() is
+ * called.
+ */
+void ewk_tile_updates_clear(Ewk_Tile *t)
+{
+ /* TODO: remove from list of pending updates? */
+ t->stats.misses = 0;
+
+ if (t->stats.full_update)
+ t->stats.full_update = 0;
+ else if (t->updates) {
+ eina_tiler_free(t->updates);
+ t->updates = NULL;
+ }
+}
+
+typedef struct _Ewk_Tile_Unused_Cache_Entry Ewk_Tile_Unused_Cache_Entry;
+struct _Ewk_Tile_Unused_Cache_Entry {
+ Ewk_Tile *tile;
+ int weight;
+ struct {
+ void (*cb)(void *data, Ewk_Tile *t);
+ void *data;
+ } tile_free;
+};
+
+struct _Ewk_Tile_Unused_Cache {
+ struct {
+ Eina_List *list;
+ size_t count;
+ size_t allocated;
+ } entries;
+ struct {
+ size_t max; /**< watermark (in bytes) to start freeing tiles */
+ size_t used; /**< in bytes, maybe more than max. */
+ } memory;
+ struct {
+ Evas_Coord x, y, w, h;
+ float zoom;
+ Eina_Bool locked;
+ } locked;
+ int references;
+ unsigned int frozen;
+ Eina_Bool dirty:1;
+};
+
+static const size_t TILE_UNUSED_CACHE_ALLOCATE_INITIAL = 128;
+static const size_t TILE_UNUSED_CACHE_ALLOCATE_STEP = 16;
+static const size_t TILE_UNUSED_CACHE_MAX_FREE = 32;
+
+/**
+ * Cache of unused tiles (those that are not visible).
+ *
+ * The cache of unused tiles.
+ *
+ * @param max cache size in bytes.
+ *
+ * @return newly allocated cache of unused tiles, use
+ * ewk_tile_unused_cache_free() to release resources. If not
+ * possible to allocate memory, @c NULL is returned.
+ */
+Ewk_Tile_Unused_Cache *ewk_tile_unused_cache_new(size_t max)
+{
+ Ewk_Tile_Unused_Cache *tuc;
+
+ CALLOC_OR_OOM_RET(tuc, sizeof(Ewk_Tile_Unused_Cache), NULL);
+
+ DBG("tuc=%p", tuc);
+ tuc->memory.max = max;
+ tuc->references = 1;
+ return tuc;
+}
+
+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)
+{
+ EINA_SAFETY_ON_NULL_RETURN(tuc);
+
+ tuc->locked.locked = EINA_TRUE;
+ tuc->locked.x = x;
+ tuc->locked.y = y;
+ tuc->locked.w = w;
+ tuc->locked.h = h;
+ tuc->locked.zoom = zoom;
+}
+
+void ewk_tile_unused_cache_unlock_area(Ewk_Tile_Unused_Cache *tuc)
+{
+ EINA_SAFETY_ON_NULL_RETURN(tuc);
+
+ tuc->locked.locked = EINA_FALSE;
+}
+
+/**
+ * Free cache of unused tiles.
+ *
+ * Those tiles that are still visible will remain live. The unused
+ * tiles will be freed.
+ *
+ * @see ewk_tile_unused_cache_unref()
+ */
+void ewk_tile_unused_cache_free(Ewk_Tile_Unused_Cache *tuc)
+{
+ EINA_SAFETY_ON_NULL_RETURN(tuc);
+
+ DBG("tuc=%p, "
+ "entries=(count:%zd, allocated:%zd), "
+ "memory=(max:%zd, used:%zd)",
+ tuc, tuc->entries.count, tuc->entries.allocated,
+ tuc->memory.max, tuc->memory.used);
+
+ ewk_tile_unused_cache_clear(tuc);
+ free(tuc);
+}
+
+/**
+ * Clear cache of unused tiles.
+ *
+ * Any tiles that are in the cache are freed. The only tiles that are
+ * kept are those that aren't in the cache (i.e. that are visible).
+ */
+void ewk_tile_unused_cache_clear(Ewk_Tile_Unused_Cache *tuc)
+{
+ Ewk_Tile_Unused_Cache_Entry *itr;
+ EINA_SAFETY_ON_NULL_RETURN(tuc);
+
+ if (!tuc->entries.count)
+ return;
+
+ EINA_LIST_FREE(tuc->entries.list, itr) {
+ itr->tile_free.cb(itr->tile_free.data, itr->tile);
+ free(itr);
+ }
+
+ tuc->memory.used = 0;
+ tuc->entries.count = 0;
+ tuc->dirty = EINA_FALSE;
+}
+
+/**
+ * Hold reference to cache.
+ *
+ * @return same pointer as taken.
+ *
+ * @see ewk_tile_unused_cache_unref()
+ */
+Ewk_Tile_Unused_Cache *ewk_tile_unused_cache_ref(Ewk_Tile_Unused_Cache *tuc)
+{
+ EINA_SAFETY_ON_NULL_RETURN_VAL(tuc, NULL);
+ tuc->references++;
+ return tuc;
+}
+
+/**
+ * Release cache reference, freeing it if it drops to zero.
+ *
+ * @see ewk_tile_unused_cache_ref()
+ * @see ewk_tile_unused_cache_free()
+ */
+void ewk_tile_unused_cache_unref(Ewk_Tile_Unused_Cache *tuc)
+{
+ EINA_SAFETY_ON_NULL_RETURN(tuc);
+ tuc->references--;
+ if (!tuc->references)
+ ewk_tile_unused_cache_free(tuc);
+}
+
+/**
+ * Change cache capacity, in bytes.
+ *
+ * This will not flush cache, use ewk_tile_unused_cache_flush() or
+ * ewk_tile_unused_cache_auto_flush() to do so.
+ */
+void ewk_tile_unused_cache_max_set(Ewk_Tile_Unused_Cache *tuc, size_t max)
+{
+ EINA_SAFETY_ON_NULL_RETURN(tuc);
+ tuc->memory.max = max;
+}
+
+/**
+ * Retrieve maximum cache capacity, in bytes.
+ */
+size_t ewk_tile_unused_cache_max_get(const Ewk_Tile_Unused_Cache *tuc)
+{
+ EINA_SAFETY_ON_NULL_RETURN_VAL(tuc, 0);
+ return tuc->memory.max;
+}
+
+/**
+ * Retrieve the used cache capacity, in bytes.
+ */
+size_t ewk_tile_unused_cache_used_get(const Ewk_Tile_Unused_Cache *tuc)
+{
+ EINA_SAFETY_ON_NULL_RETURN_VAL(tuc, 0);
+ return tuc->memory.used;
+}
+
+/**
+ * Flush given amount of bytes from cache.
+ *
+ * After calling this function, near @a bytes are freed from cache. It
+ * may be less if cache did not contain that amount of bytes (ie: an
+ * empty cache has nothing to free!) or more if the cache just
+ * contained objects that were larger than the requested amount (this
+ * is usually the case).
+ *
+ * @param tuc cache of unused tiles.
+ * @param bytes amount to free.
+ *
+ * @return amount really freed.
+ *
+ * @see ewk_tile_unused_cache_used_get()
+ */
+size_t ewk_tile_unused_cache_flush(Ewk_Tile_Unused_Cache *tuc, size_t bytes)
+{
+ Ewk_Tile_Unused_Cache_Entry *itr;
+ Eina_List *l, *l_next;
+ EINA_SAFETY_ON_NULL_RETURN_VAL(tuc, 0);
+ size_t done;
+ unsigned int count;
+
+ if (!tuc->entries.count)
+ return 0;
+ if (bytes < 1)
+ return 0;
+
+ /*
+ * NOTE: the cache is a FIFO queue currently.
+ * Don't need to sort any more.
+ */
+
+ if (tuc->dirty)
+ tuc->dirty = EINA_FALSE;
+
+ done = 0;
+ count = 0;
+ EINA_LIST_FOREACH_SAFE(tuc->entries.list, l, l_next, itr) {
+ Ewk_Tile *t = itr->tile;
+ if (done > bytes)
+ break;
+ if (tuc->locked.locked
+ && t->x + t->w > tuc->locked.x
+ && t->y + t->h > tuc->locked.y
+ && t->x < tuc->locked.x + tuc->locked.w
+ && t->y < tuc->locked.y + tuc->locked.h
+ && t->zoom == tuc->locked.zoom) {
+ continue;
+ }
+ done += sizeof(Ewk_Tile) + itr->tile->bytes;
+ itr->tile_free.cb(itr->tile_free.data, itr->tile);
+ tuc->entries.list = eina_list_remove_list(tuc->entries.list, l);
+ free(itr);
+ count++;
+ }
+
+ tuc->memory.used -= done;
+ tuc->entries.count -= count;
+
+ return done;
+}
+
+/**
+ * Flush enough bytes to make cache usage lower than maximum.
+ *
+ * Just like ewk_tile_unused_cache_flush(), but this will make the cache
+ * free enough tiles to respect maximum cache size as defined with
+ * ewk_tile_unused_cache_max_set().
+ *
+ * This function is usually called when system becomes idle. This way
+ * we keep memory low but do not impact performance when
+ * creating/deleting tiles.
+ */
+void ewk_tile_unused_cache_auto_flush(Ewk_Tile_Unused_Cache *tuc)
+{
+ EINA_SAFETY_ON_NULL_RETURN(tuc);
+ if (tuc->memory.used <= tuc->memory.max)
+ return;
+ ewk_tile_unused_cache_flush(tuc, tuc->memory.used - tuc->memory.max);
+ if (tuc->memory.used > tuc->memory.max)
+ CRITICAL("Cache still using too much memory: %zd KB; max: %zd KB",
+ tuc->memory.used, tuc->memory.max);
+}
+
+/**
+ * Flag cache as dirty.
+ *
+ * If cache is dirty then next flush operations will have to recompute
+ * weight and sort again to find the best tiles to expire.
+ *
+ * One must call this function when tile properties that may change
+ * likeness of tile to be flushed change, like Tile::stats.
+ */
+void ewk_tile_unused_cache_dirty(Ewk_Tile_Unused_Cache *tuc)
+{
+ tuc->dirty = EINA_TRUE;
+}
+
+/**
+ * Freeze cache to not do maintenance tasks.
+ *
+ * Maintenance tasks optimize cache usage, but maybe we know we should
+ * hold on them until we do the last operation, in this case we freeze
+ * while operating and then thaw when we're done.
+ *
+ * @see ewk_tile_unused_cache_thaw()
+ */
+void ewk_tile_unused_cache_freeze(Ewk_Tile_Unused_Cache *tuc)
+{
+ tuc->frozen++;
+}
+
+/**
+ * Unfreezes maintenance tasks.
+ *
+ * If this is the last counterpart of freeze, then maintenance tasks
+ * will run immediately.
+ */
+void ewk_tile_unused_cache_thaw(Ewk_Tile_Unused_Cache *tuc)
+{
+ if (!tuc->frozen) {
+ ERR("thawing more than freezing!");
+ return;
+ }
+
+ tuc->frozen--;
+}
+
+/**
+ * Get tile from cache of unused tiles, removing it from the cache.
+ *
+ * If the tile is used, then it's not in cache of unused tiles, so it
+ * is removed from the cache and may be given back with
+ * ewk_tile_unused_cache_tile_put().
+ *
+ * @param tuc cache of unused tiles
+ * @param t the tile to be removed from Ewk_Tile_Unused_Cache.
+ *
+ * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
+ */
+Eina_Bool ewk_tile_unused_cache_tile_get(Ewk_Tile_Unused_Cache *tuc, Ewk_Tile *t)
+{
+ Ewk_Tile_Unused_Cache_Entry *entry;
+ Eina_List *e, *l;
+
+ e = NULL;
+ EINA_LIST_FOREACH(tuc->entries.list, l, entry)
+ {
+ if (entry->tile == t) {
+ e = l;
+ break;
+ }
+ }
+ if (!e) {
+ ERR("tile %p not found in cache %p", t, tuc);
+ return EINA_FALSE;
+ }
+
+ tuc->entries.count--;
+ tuc->memory.used -= sizeof(Ewk_Tile) + t->bytes;
+ tuc->entries.list = eina_list_remove_list(tuc->entries.list, e);
+ free(entry);
+ // TODO assume dirty for now, but may it's not,
+ // if the item was at the beginning of the queue
+ tuc->dirty = EINA_TRUE;
+
+ return EINA_TRUE;
+}
+
+/**
+ * Put tile into cache of unused tiles, adding it to the cache.
+ *
+ * This should be called when @c t->visible is @c 0 and no objects are
+ * using the tile anymore, making it available to be expired and have
+ * its memory replaced.
+ *
+ * Note that tiles are not automatically deleted if cache is full,
+ * instead the cache will have more bytes used than maximum and one
+ * can call ewk_tile_unused_cache_auto_flush() to free them. This is done
+ * because usually we want a lazy operation for better performance.
+ *
+ * @param tuc cache of unused tiles
+ * @param t tile to be added to cache.
+ * @param tile_free_cb function used to free tiles.
+ * @param data context to give back to @a tile_free_cb as first argument.
+ *
+ * @return #EINA_TRUE on success, #EINA_FALSE otherwise. If @c t->visible
+ * is not #EINA_FALSE, then it will return #EINA_FALSE.
+ *
+ * @see ewk_tile_unused_cache_auto_flush()
+ */
+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)
+{
+ Ewk_Tile_Unused_Cache_Entry *e;
+
+ if (t->visible) {
+ ERR("tile=%p is not unused (visible=%d)", t, t->visible);
+ return EINA_FALSE;
+ }
+
+ MALLOC_OR_OOM_RET(e, sizeof(Ewk_Tile_Unused_Cache_Entry), EINA_FALSE);
+ tuc->entries.list = eina_list_append(tuc->entries.list, e);
+ if (eina_error_get()) {
+ ERR("List allocation failed");
+ return EINA_FALSE;
+ }
+
+ e->tile = t;
+ e->weight = 0; /* calculated just before sort */
+ e->tile_free.cb = tile_free_cb;
+ e->tile_free.data = (void *)data;
+
+ tuc->entries.count++;
+ tuc->memory.used += sizeof(Ewk_Tile) + t->bytes;
+ tuc->dirty = EINA_TRUE;
+
+ return EINA_TRUE;
+}
+
+void ewk_tile_unused_cache_dbg(const Ewk_Tile_Unused_Cache *tuc)
+{
+ Ewk_Tile_Unused_Cache_Entry *itr;
+ Eina_List *l;
+ int count = 0;
+ printf("Cache of unused tiles: entries: %zu/%zu, memory: %zu/%zu\n",
+ tuc->entries.count, tuc->entries.allocated,
+ tuc->memory.used, tuc->memory.max);
+
+ EINA_LIST_FOREACH(tuc->entries.list, l, itr) {
+ const Ewk_Tile *t = itr->tile;
+ printf(" [%3lu,%3lu + %dx%d @ %0.3f]%c",
+ t->col, t->row, t->w, t->h, t->zoom,
+ t->visible ? '*': ' ');
+
+ if (!(count % 4))
+ printf("\n");
+ }
+
+ printf("\n");
+}