| /* |
| 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"); |
| } |