diff options
Diffstat (limited to 'WebKit/efl/ewk')
-rw-r--r-- | WebKit/efl/ewk/ewk_tiled_backing_store.c | 2137 | ||||
-rw-r--r-- | WebKit/efl/ewk/ewk_tiled_backing_store.h | 130 | ||||
-rw-r--r-- | WebKit/efl/ewk/ewk_tiled_matrix.c | 771 | ||||
-rw-r--r-- | WebKit/efl/ewk/ewk_tiled_matrix.h | 60 | ||||
-rw-r--r-- | WebKit/efl/ewk/ewk_tiled_model.c | 905 | ||||
-rw-r--r-- | WebKit/efl/ewk/ewk_tiled_model.h | 58 | ||||
-rw-r--r-- | WebKit/efl/ewk/ewk_tiled_private.h | 62 | ||||
-rw-r--r-- | WebKit/efl/ewk/ewk_view.cpp | 162 | ||||
-rw-r--r-- | WebKit/efl/ewk/ewk_view.h | 36 | ||||
-rw-r--r-- | WebKit/efl/ewk/ewk_view_tiled.c | 346 |
10 files changed, 4664 insertions, 3 deletions
diff --git a/WebKit/efl/ewk/ewk_tiled_backing_store.c b/WebKit/efl/ewk/ewk_tiled_backing_store.c new file mode 100644 index 0000000..0e8898b --- /dev/null +++ b/WebKit/efl/ewk/ewk_tiled_backing_store.c @@ -0,0 +1,2137 @@ +/* + 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_backing_store.h" + +#define _GNU_SOURCE +#include "ewk_tiled_private.h" +#include <Ecore.h> +#include <Eina.h> +#include <errno.h> +#include <math.h> +#include <stdio.h> // XXX REMOVE ME LATER +#include <stdlib.h> +#include <string.h> + +#define IDX(col, row, rowspan) (col + (row * rowspan)) + +#if !defined(MIN) +# define MIN(a, b) ((a < b) ? a : b) +#endif + +#if !defined(MAX) +# define MAX(a, b) ((a > b) ? a : b) +#endif + +typedef enum _Ewk_Tiled_Backing_Store_Pre_Render_Priority Ewk_Tiled_Backing_Store_Pre_Render_Priority; +typedef struct _Ewk_Tiled_Backing_Store_Data Ewk_Tiled_Backing_Store_Data; +typedef struct _Ewk_Tiled_Backing_Store_Item Ewk_Tiled_Backing_Store_Item; +typedef struct _Ewk_Tiled_Backing_Store_Pre_Render_Request Ewk_Tiled_Backing_Store_Pre_Render_Request; + +enum _Ewk_Tiled_Backing_Store_Pre_Render_Priority { + PRE_RENDER_PRIORITY_LOW = 0, /**< Append the request to the list */ + PRE_RENDER_PRIORITY_HIGH /**< Prepend the request to the list */ +}; + +struct _Ewk_Tiled_Backing_Store_Item { + EINA_INLIST; + Ewk_Tile *tile; + struct { + Evas_Coord x, y, w, h; + } geometry; + struct { + Eina_List *process; + unsigned long row, col; + float zoom; + } update; + Ewk_Tiled_Backing_Store_Pre_Render_Request *pre_render; + Eina_Bool smooth_scale; +}; + +struct _Ewk_Tiled_Backing_Store_Pre_Render_Request { + EINA_INLIST; + unsigned long col, row; + float zoom; + struct _Ewk_Tiled_Backing_Store_Item *it; +}; + +struct _Ewk_Tiled_Backing_Store_Data { + Evas_Object_Smart_Clipped_Data base; + Evas_Object *self; + Evas_Object *contents_clipper; + struct { + Eina_Inlist **items; + Evas_Coord x, y, w, h; + long cols, rows; + struct { + Evas_Coord w, h; + float zoom; + Eina_Bool zoom_weak_smooth_scale:1; + } tile; + struct { + struct { + Evas_Coord x, y; + } cur, old, base, zoom_center; + } offset; + } view; + Evas_Colorspace cspace; + struct { + Ewk_Tile_Matrix *matrix; + struct { + unsigned long col, row; + } base; + struct { + unsigned long cols, rows; + } cur, old; + Evas_Coord width, height; + } model; + struct { + Eina_Bool (*cb)(void *data, Ewk_Tile *t, const Eina_Rectangle *area); + void *data; + Eina_List *queue; + Eina_Bool process_entire_queue; + Eina_Inlist *pre_render_requests; + Ecore_Idler *idler; + Eina_Bool disabled; + Eina_Bool suspend:1; + } render; + struct { + void *(*pre_cb)(void *data, Evas_Object *o); + void *pre_data; + void *(*post_cb)(void *data, void *pre_data, Evas_Object *o); + void *post_data; + } process; + struct { + Eina_Bool any:1; + Eina_Bool pos:1; + Eina_Bool size:1; + Eina_Bool model:1; + Eina_Bool offset:1; + } changed; +#ifdef DEBUG_MEM_LEAKS + Ecore_Event_Handler *sig_usr; +#endif +}; + +static Evas_Smart_Class _parent_sc = EVAS_SMART_CLASS_INIT_NULL; +int _ewk_tiled_log_dom = -1; + +#define PRIV_DATA_GET_OR_RETURN(obj, ptr, ...) \ + Ewk_Tiled_Backing_Store_Data *ptr = evas_object_smart_data_get(obj); \ + if (!ptr) { \ + CRITICAL("no private data in obj=%p", obj); \ + return __VA_ARGS__; \ + } + +static inline void _ewk_tiled_backing_store_item_request_del(Ewk_Tiled_Backing_Store_Data *priv, Ewk_Tiled_Backing_Store_Item *it); +static inline void _ewk_tiled_backing_store_item_request_add(Ewk_Tiled_Backing_Store_Data *priv, Ewk_Tiled_Backing_Store_Item *it, int m_col, int m_row, float zoom); +static void _ewk_tiled_backing_store_fill_renderers(Ewk_Tiled_Backing_Store_Data *priv); +static inline void _ewk_tiled_backing_store_view_dbg(const Ewk_Tiled_Backing_Store_Data *priv); +static inline void _ewk_tiled_backing_store_changed(Ewk_Tiled_Backing_Store_Data *priv); + +static inline void _ewk_tiled_backing_store_updates_process(Ewk_Tiled_Backing_Store_Data *priv) +{ + void *data = NULL; + + /* Do not process updates. Note that we still want to get updates requests + * in the queue in order to not miss any updates after the render is + * resumed. + */ + if (priv->render.suspend || !evas_object_visible_get(priv->self)) + return; + + if (priv->process.pre_cb) + data = priv->process.pre_cb(priv->process.pre_data, priv->self); + + ewk_tile_matrix_updates_process(priv->model.matrix); + + if (priv->process.post_cb) + priv->process.post_cb(priv->process.post_data, data, priv->self); +} + +static int _ewk_tiled_backing_store_flush(void *data) +{ + Ewk_Tiled_Backing_Store_Data *priv = data; + Ewk_Tile_Unused_Cache *tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix); + + if (tuc) { + DBG("flush unused tile cache."); + ewk_tile_unused_cache_auto_flush(tuc); + } else + ERR("no cache?!"); + + return 0; +} + +static Ewk_Tile *_ewk_tiled_backing_store_tile_new(Ewk_Tiled_Backing_Store_Data *priv, unsigned long col, unsigned long row, float zoom) +{ + Ewk_Tile *t; + Evas *evas = evas_object_evas_get(priv->self); + if (!evas) { + CRITICAL("evas_object_evas_get failed!"); + return NULL; + } + + t = ewk_tile_matrix_tile_new + (priv->model.matrix, evas, col, row, zoom); + + if (!t) { + CRITICAL("ewk_tile_matrix_tile_new failed!"); + return NULL; + } + + return t; +} + +static void _ewk_tiled_backing_store_item_move(Ewk_Tiled_Backing_Store_Item *it, Evas_Coord x, Evas_Coord y) +{ + it->geometry.x = x; + it->geometry.y = y; + + if (it->tile) + evas_object_move(it->tile->image, x, y); +} + +static void _ewk_tiled_backing_store_item_resize(Ewk_Tiled_Backing_Store_Item *it, Evas_Coord w, Evas_Coord h) +{ + it->geometry.w = w; + it->geometry.h = h; + + if (it->tile) { + evas_object_resize(it->tile->image, w, h); + evas_object_image_fill_set(it->tile->image, 0, 0, w, h); + } +} + +static void _ewk_tiled_backing_store_tile_associate(Ewk_Tiled_Backing_Store_Data *priv, Ewk_Tile *t, Ewk_Tiled_Backing_Store_Item *it) +{ + if (it->tile) + CRITICAL("it->tile=%p, but it should be NULL!", it->tile); + it->tile = t; + evas_object_move(it->tile->image, it->geometry.x, it->geometry.y); + evas_object_resize(it->tile->image, it->geometry.w, it->geometry.h); + evas_object_image_fill_set + (it->tile->image, 0, 0, it->geometry.w, it->geometry.h); + evas_object_image_smooth_scale_set(it->tile->image, it->smooth_scale); + + if (!ewk_tile_visible_get(t)) + evas_object_smart_member_add(t->image, priv->self); + + ewk_tile_show(t); +} + +static void _ewk_tiled_backing_store_tile_dissociate(Ewk_Tiled_Backing_Store_Data *priv, Ewk_Tiled_Backing_Store_Item *it, double last_used) +{ + Ewk_Tile_Unused_Cache *tuc; + ewk_tile_hide(it->tile); + if (!ewk_tile_visible_get(it->tile)) + evas_object_smart_member_del(it->tile->image); + ewk_tile_matrix_tile_put(priv->model.matrix, it->tile, last_used); + tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix); + ewk_tile_unused_cache_auto_flush(tuc); + + it->tile = NULL; +} + +static void _ewk_tiled_backing_store_tile_dissociate_all(Ewk_Tiled_Backing_Store_Data *priv) +{ + Eina_Inlist *it; + Ewk_Tiled_Backing_Store_Item *item; + int i; + double last_used = ecore_loop_time_get(); + + for (i = 0; i < priv->view.rows; i++) { + it = priv->view.items[i]; + EINA_INLIST_FOREACH(it, item) + if (item->tile) + _ewk_tiled_backing_store_tile_dissociate(priv, item, last_used); + } +} + +static inline Eina_Bool _ewk_tiled_backing_store_pre_render_request_add(Ewk_Tiled_Backing_Store_Data *priv, unsigned long col, unsigned long row, float zoom, Ewk_Tiled_Backing_Store_Item *it, Ewk_Tiled_Backing_Store_Pre_Render_Priority priority) +{ + Ewk_Tiled_Backing_Store_Pre_Render_Request *r; + + MALLOC_OR_OOM_RET(r, sizeof(*r), EINA_FALSE); + + if (priority == PRE_RENDER_PRIORITY_HIGH) + priv->render.pre_render_requests = eina_inlist_prepend + (priv->render.pre_render_requests, EINA_INLIST_GET(r)); + else + priv->render.pre_render_requests = eina_inlist_append + (priv->render.pre_render_requests, EINA_INLIST_GET(r)); + + r->col = col; + r->row = row; + r->zoom = zoom; + r->it = it; + + if (it) + it->pre_render = r; + return EINA_TRUE; +} + +static inline void _ewk_tiled_backing_store_pre_render_request_del(Ewk_Tiled_Backing_Store_Data *priv, Ewk_Tiled_Backing_Store_Pre_Render_Request *r) +{ + priv->render.pre_render_requests = eina_inlist_remove + (priv->render.pre_render_requests, EINA_INLIST_GET(r)); + free(r); +} + +static inline Ewk_Tiled_Backing_Store_Pre_Render_Request *_ewk_tiled_backing_store_pre_render_request_first(const Ewk_Tiled_Backing_Store_Data *priv) +{ + return EINA_INLIST_CONTAINER_GET( + priv->render.pre_render_requests, + Ewk_Tiled_Backing_Store_Pre_Render_Request); +} + +static void _ewk_tiled_backing_store_pre_render_request_flush(Ewk_Tiled_Backing_Store_Data *priv) +{ + Eina_Inlist **pl = &priv->render.pre_render_requests; + while (*pl) { + Ewk_Tiled_Backing_Store_Pre_Render_Request *r; + r = _ewk_tiled_backing_store_pre_render_request_first(priv); + if (r->it && r->it->pre_render) + r->it->pre_render = NULL; + *pl = eina_inlist_remove(*pl, *pl); + free(r); + } +} + +static void _ewk_tiled_backing_store_pre_render_request_remove_unassociated(Ewk_Tiled_Backing_Store_Data *priv) +{ + Eina_Inlist **pl = &priv->render.pre_render_requests; + Eina_Inlist *iter = *pl, *tmp; + while (iter) { + Ewk_Tiled_Backing_Store_Pre_Render_Request *r = + EINA_INLIST_CONTAINER_GET( + iter, Ewk_Tiled_Backing_Store_Pre_Render_Request); + if (!r->it) { + tmp = iter->next; + *pl = eina_inlist_remove(*pl, iter); + iter = tmp; + free(r); + } else + iter = iter->next; + } +} + +static void _ewk_tiled_backing_store_pre_render_request_remove_associated(Ewk_Tiled_Backing_Store_Data *priv) +{ + Eina_Inlist **pl = &priv->render.pre_render_requests; + Eina_Inlist *iter = *pl, *tmp; + while (iter) { + Ewk_Tiled_Backing_Store_Pre_Render_Request *r = + EINA_INLIST_CONTAINER_GET( + iter, Ewk_Tiled_Backing_Store_Pre_Render_Request); + if (r->it) { + if (r->it->pre_render) + r->it->pre_render = NULL; + tmp = iter->next; + *pl = eina_inlist_remove(*pl, iter); + iter = tmp; + free(r); + } else + iter = iter->next; + } +} + +/* assumes priv->process.pre_cb was called if required! */ +static void _ewk_tiled_backing_store_pre_render_request_process_single(Ewk_Tiled_Backing_Store_Data *priv) +{ + Ewk_Tiled_Backing_Store_Pre_Render_Request *req; + Eina_Rectangle area; + Ewk_Tile_Matrix *tm = priv->model.matrix; + Ewk_Tile *t; + Ewk_Tile_Unused_Cache *tuc; + unsigned long col, row; + float zoom; + double last_used = ecore_loop_time_get(); + + req = _ewk_tiled_backing_store_pre_render_request_first(priv); + if (!req) + return; + + col = req->col; + row = req->row; + zoom = req->zoom; + + if (ewk_tile_matrix_tile_exact_exists(tm, col, row, zoom)) { + DBG("no pre-render required for tile %lu,%lu @ %f.", col, row, zoom); + goto end; + } + + if (req->it && req->it->tile) { + CRITICAL("it->tile = %p (%lu, %lu), but should be NULL", req->it->tile, req->it->tile->row, req->it->tile->col); + goto end; + } + + t = _ewk_tiled_backing_store_tile_new(priv, col, row, zoom); + if (!t) + goto end; + + area.x = 0; + area.y = 0; + area.w = priv->view.tile.w; + area.h = priv->view.tile.h; + + priv->render.cb(priv->render.data, t, &area); + evas_object_image_data_update_add( + t->image, + area.x, area.y, area.w, area.h); + ewk_tile_matrix_tile_updates_clear(tm, t); + + if (req->it) { + _ewk_tiled_backing_store_tile_associate(priv, t, req->it); + if (req->it->pre_render) + req->it->pre_render = NULL; + } else + ewk_tile_matrix_tile_put(tm, t, last_used); + +end: + _ewk_tiled_backing_store_pre_render_request_del(priv, req); + tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix); + ewk_tile_unused_cache_auto_flush(tuc); +} + +static Eina_Bool _ewk_tiled_backing_store_item_process_idler_cb(void *data) +{ + Ewk_Tiled_Backing_Store_Data *priv = data; + Ewk_Tiled_Backing_Store_Item *it = NULL; + + while (priv->render.queue) { + it = priv->render.queue->data; + if (it->tile->zoom == priv->view.tile.zoom) { + _ewk_tiled_backing_store_item_request_del(priv, it); + it = NULL; + } else { + unsigned long row, col; + float zoom; + Ewk_Tile *t; + if (it->tile) { + double last_used = ecore_loop_time_get(); + _ewk_tiled_backing_store_tile_dissociate(priv, it, last_used); + } + + row = it->update.row; + col = it->update.col; + zoom = it->update.zoom; + t = _ewk_tiled_backing_store_tile_new(priv, col, row, zoom); + if (!t) { + priv->render.idler = NULL; + return EINA_FALSE; + } + + _ewk_tiled_backing_store_tile_associate(priv, t, it); + it->update.process = NULL; + priv->render.queue = eina_list_remove_list(priv->render.queue, + priv->render.queue); + if (!priv->render.process_entire_queue) + break; + } + } + + if (priv->process.pre_cb) + data = priv->process.pre_cb(priv->process.pre_data, priv->self); + + ewk_tile_matrix_updates_process(priv->model.matrix); + + if (!it) + _ewk_tiled_backing_store_pre_render_request_process_single(priv); + + if (priv->process.post_cb) + priv->process.post_cb(priv->process.post_data, data, priv->self); + + if (!priv->render.queue && !priv->render.pre_render_requests) { + priv->render.idler = NULL; + return EINA_FALSE; + } + + return EINA_TRUE; +} + +static inline void _ewk_tiled_backing_store_item_process_idler_stop(Ewk_Tiled_Backing_Store_Data *priv) +{ + if (!priv->render.idler) + return; + + ecore_idler_del(priv->render.idler); + priv->render.idler = NULL; +} + +static inline void _ewk_tiled_backing_store_item_process_idler_start(Ewk_Tiled_Backing_Store_Data *priv) +{ + if (priv->render.idler) + return; + priv->render.idler = ecore_idler_add( + _ewk_tiled_backing_store_item_process_idler_cb, priv); +} + +static inline void _ewk_tiled_backing_store_item_request_del(Ewk_Tiled_Backing_Store_Data *priv, Ewk_Tiled_Backing_Store_Item *it) +{ + priv->render.queue = eina_list_remove_list(priv->render.queue, + it->update.process); + it->update.process = NULL; +} + +static inline void _ewk_tiled_backing_store_item_request_add(Ewk_Tiled_Backing_Store_Data *priv, Ewk_Tiled_Backing_Store_Item *it, int m_col, int m_row, float zoom) +{ + if (it->update.process) + return; + + it->update.col = m_col; + it->update.row = m_row; + it->update.zoom = zoom; + + priv->render.queue = eina_list_append(priv->render.queue, it); + it->update.process = eina_list_last(priv->render.queue); + + if (!priv->render.suspend) + _ewk_tiled_backing_store_item_process_idler_start(priv); +} + +static Eina_Bool _ewk_tiled_backing_store_disable_render(Ewk_Tiled_Backing_Store_Data *priv) +{ + if (priv->render.suspend) + return EINA_TRUE; + + priv->render.suspend = EINA_TRUE; + _ewk_tiled_backing_store_item_process_idler_stop(priv); + return EINA_TRUE; +} + +static Eina_Bool _ewk_tiled_backing_store_enable_render(Ewk_Tiled_Backing_Store_Data *priv) +{ + if (!priv->render.suspend) + return EINA_TRUE; + + priv->render.suspend = EINA_FALSE; + + _ewk_tiled_backing_store_fill_renderers(priv); + _ewk_tiled_backing_store_item_process_idler_start(priv); + + return EINA_TRUE; +} + +/** + * Returns a rectangle whose coordinates indicate which tiles are + * currently inside the viewport. + * + * Specifically, the returned rectangle's coordinates have the + * following meaning: + * - x: Column number for the top-level tile inside the viewport. + * - y: Row number for the top-level tile inside the viewport. + * - w: Number of tiles horizontally inside the viewport. + * - h: Number of tiles vertically inside the viewport. + */ +static Eina_Rectangle _ewk_tiled_backing_store_visible_tiles_rect(Ewk_Tiled_Backing_Store_Data *priv) +{ + Eina_Rectangle r; + + const Evas_Coord ox = -priv->view.offset.cur.x; + const Evas_Coord oy = -priv->view.offset.cur.y; + const Evas_Coord tw = priv->view.tile.w; + const Evas_Coord th = priv->view.tile.h; + + r.x = MAX(0, ox / tw); + r.y = MAX(0, oy / th); + r.w = MIN(priv->model.cur.cols, + ceil((float)(priv->view.w + (ox % tw)) / tw)); + r.h = MIN(priv->model.cur.rows, + ceil((float)(priv->view.h + (oy % th)) / th)); + + DBG("Returning %d,%d+%dx%d", r.x, r.y, r.w, r.h); + + return r; +} + +static Eina_Bool _ewk_tiled_backing_store_tile_is_inside_viewport(Ewk_Tiled_Backing_Store_Data *priv, int col, int row) +{ + const Eina_Rectangle r = _ewk_tiled_backing_store_visible_tiles_rect(priv); + + return eina_rectangle_coords_inside(&r, col, row); +} + +static Eina_Bool _ewk_tiled_backing_store_tile_is_adjacent_to_viewport(Ewk_Tiled_Backing_Store_Data *priv, int col, int row) +{ + const Eina_Rectangle r = _ewk_tiled_backing_store_visible_tiles_rect(priv); + + if (row == (r.y - 1) || row == (r.y + r.h)) + return (col >= (r.x - 1) && col <= (r.x + r.w)); + + if (col == (r.x - 1) || col == (r.x + r.w)) + return (row >= (r.y - 1) && row <= (r.y + r.h)); + + return EINA_FALSE; +} + +static inline Eina_Bool _ewk_tiled_backing_store_item_fill(Ewk_Tiled_Backing_Store_Data *priv, Ewk_Tiled_Backing_Store_Item *it, long col, int row) +{ + int m_col = priv->model.base.col + col; + int m_row = priv->model.base.row + row; + double last_used = ecore_loop_time_get(); + + if (m_col < 0 || m_row < 0 + || m_col >= priv->model.cur.cols || m_row >= priv->model.cur.rows) { + + if (it->tile) { + _ewk_tiled_backing_store_tile_dissociate(priv, it, last_used); + if (it->update.process) + _ewk_tiled_backing_store_item_request_del(priv, it); + } + } else { + Ewk_Tile *t; + const float zoom = priv->view.tile.zoom; + + if (it->update.process) { + if (it->update.row == m_row + && it->update.col == m_col + && it->update.zoom == zoom) + return EINA_TRUE; + + _ewk_tiled_backing_store_item_request_del(priv, it); + } + + if (it->pre_render) { + _ewk_tiled_backing_store_pre_render_request_del( + priv, it->pre_render); + it->pre_render = NULL; + } + + if (it->tile) { + Ewk_Tile *old = it->tile; + if (old->row != m_row || old->col != m_col || old->zoom != zoom) { + _ewk_tiled_backing_store_tile_dissociate(priv, it, + last_used); + if (it->update.process) + _ewk_tiled_backing_store_item_request_del(priv, it); + } else if (old->row == m_row && old->col == m_col + && old->zoom == zoom) + goto end; + } + + t = ewk_tile_matrix_tile_exact_get + (priv->model.matrix, m_col, m_row, zoom); + if (!t) { + /* NOTE: it never returns NULL if it->tile was set! */ + if (it->tile) { + CRITICAL("it->tile=%p, but it should be NULL!", it->tile); + _ewk_tiled_backing_store_tile_dissociate(priv, it, + last_used); + } + + /* Do not add new requests to the render queue */ + if (!priv->render.suspend) { + if (!_ewk_tiled_backing_store_tile_is_inside_viewport( + priv, m_col, m_row)) { + DBG("%d,%d is not inside the viewport", m_col, m_row); + if (_ewk_tiled_backing_store_tile_is_adjacent_to_viewport( + priv, m_col, m_row)) + _ewk_tiled_backing_store_pre_render_request_add( + priv, m_col, m_row, zoom, it, + PRE_RENDER_PRIORITY_HIGH); + _ewk_tiled_backing_store_item_process_idler_start(priv); + + goto end; + } + + t = _ewk_tiled_backing_store_tile_new(priv, m_col, m_row, zoom); + if (!t) + return EINA_FALSE; + _ewk_tiled_backing_store_tile_associate(priv, t, it); + } + } else if (t != it->tile) { + if (!it->update.process) { + if (it->tile) + _ewk_tiled_backing_store_tile_dissociate(priv, + it, last_used); + _ewk_tiled_backing_store_tile_associate(priv, t, it); + } + } + + end: + + return EINA_TRUE; + } + + return EINA_TRUE; +} + +static Ewk_Tiled_Backing_Store_Item *_ewk_tiled_backing_store_item_add(Ewk_Tiled_Backing_Store_Data *priv, long col, int row) +{ + Ewk_Tiled_Backing_Store_Item *it; + Evas_Coord x, y, tw, th; + + DBG("o=%p", priv->self); + + MALLOC_OR_OOM_RET(it, sizeof(*it), NULL); + + tw = priv->view.tile.w; + th = priv->view.tile.h; + x = priv->view.offset.base.x + priv->view.x + tw *col; + y = priv->view.offset.base.y + priv->view.y + th *row; + + it->tile = NULL; + it->update.process = NULL; + it->smooth_scale = priv->view.tile.zoom_weak_smooth_scale; + it->pre_render = NULL; + _ewk_tiled_backing_store_item_move(it, x, y); + _ewk_tiled_backing_store_item_resize(it, tw, th); + if (!_ewk_tiled_backing_store_item_fill(priv, it, col, row)) { + free(it); + return NULL; + } + + return it; +} + +static void _ewk_tiled_backing_store_item_del(Ewk_Tiled_Backing_Store_Data *priv, Ewk_Tiled_Backing_Store_Item *it) +{ + if (it->tile) { + double last_used = ecore_loop_time_get(); + _ewk_tiled_backing_store_tile_dissociate(priv, it, last_used); + } + if (it->update.process) + _ewk_tiled_backing_store_item_request_del(priv, it); + if (it->pre_render) { + _ewk_tiled_backing_store_pre_render_request_del( + priv, it->pre_render); + it->pre_render = NULL; + } + free(it); +} + +static void _ewk_tiled_backing_store_item_smooth_scale_set(Ewk_Tiled_Backing_Store_Item *it, Eina_Bool smooth_scale) +{ + if (it->smooth_scale == smooth_scale) + return; + + if (it->tile) + evas_object_image_smooth_scale_set(it->tile->image, smooth_scale); +} + +static inline void _ewk_tiled_backing_store_changed(Ewk_Tiled_Backing_Store_Data *priv) +{ + if (priv->changed.any) + return; + evas_object_smart_changed(priv->self); + priv->changed.any = EINA_TRUE; +} + +static void _ewk_tiled_backing_store_view_cols_end_del(Ewk_Tiled_Backing_Store_Data *priv, Eina_Inlist **p_row, unsigned int count) +{ + Eina_Inlist *n; + unsigned int i; + + if (!count) + return; + + n = (*p_row)->last; + + for (i = 0; i < count; i++) { + Ewk_Tiled_Backing_Store_Item *it; + it = EINA_INLIST_CONTAINER_GET(n, Ewk_Tiled_Backing_Store_Item); + n = n->prev; + *p_row = eina_inlist_remove(*p_row, EINA_INLIST_GET(it)); + _ewk_tiled_backing_store_item_del(priv, it); + } +} + +static Eina_Bool _ewk_tiled_backing_store_view_cols_end_add(Ewk_Tiled_Backing_Store_Data *priv, Eina_Inlist **p_row, unsigned int base_col, unsigned int count) +{ + unsigned int i, r = p_row - priv->view.items; + + for (i = 0; i < count; i++, base_col++) { + Ewk_Tiled_Backing_Store_Item *it; + + it = _ewk_tiled_backing_store_item_add(priv, base_col, r); + if (!it) { + CRITICAL("failed to add column %u of %u in row %u.", i, count, r); + _ewk_tiled_backing_store_view_cols_end_del(priv, p_row, i); + return EINA_FALSE; + } + + *p_row = eina_inlist_append(*p_row, EINA_INLIST_GET(it)); + } + return EINA_TRUE; +} + +static void _ewk_tiled_backing_store_view_row_del(Ewk_Tiled_Backing_Store_Data *priv, Eina_Inlist *row) +{ + while (row) { + Ewk_Tiled_Backing_Store_Item *it; + it = EINA_INLIST_CONTAINER_GET(row, Ewk_Tiled_Backing_Store_Item); + row = row->next; + _ewk_tiled_backing_store_item_del(priv, it); + } +} + +static void _ewk_tiled_backing_store_view_rows_range_del(Ewk_Tiled_Backing_Store_Data *priv, Eina_Inlist **start, Eina_Inlist **end) +{ + for (; start < end; start++) { + _ewk_tiled_backing_store_view_row_del(priv, *start); + *start = NULL; + } +} + +static void _ewk_tiled_backing_store_view_rows_all_del(Ewk_Tiled_Backing_Store_Data *priv) +{ + Eina_Inlist **start; + Eina_Inlist **end; + + start = priv->view.items; + end = priv->view.items + priv->view.rows; + _ewk_tiled_backing_store_view_rows_range_del(priv, start, end); + + free(priv->view.items); + priv->view.items = NULL; + priv->view.cols = 0; + priv->view.rows = 0; +} + +static void _ewk_tiled_backing_store_render(void *data, Ewk_Tile *t, const Eina_Rectangle *area) +{ + Ewk_Tiled_Backing_Store_Data *priv = data; + + INF("TODO %p (visible? %d) [%lu,%lu] %d,%d + %dx%d", + t, t->visible, t->col, t->row, area->x, area->y, area->w, area->h); + + if (!t->visible) + return; + + if (priv->view.tile.w != t->w || priv->view.tile.h != t->h) + return; // todo: remove me later, don't even flag as dirty! + + EINA_SAFETY_ON_NULL_RETURN(priv->render.cb); + if (!priv->render.cb(priv->render.data, t, area)) + return; + + evas_object_image_data_update_add(t->image, area->x, area->y, area->w, area->h); +} + +static inline void _ewk_tiled_backing_store_model_matrix_create(Ewk_Tiled_Backing_Store_Data *priv, Ewk_Tile_Unused_Cache *tuc) +{ + if (priv->model.matrix) { + _ewk_tiled_backing_store_view_rows_all_del(priv); + + priv->changed.offset = EINA_FALSE; + priv->changed.size = EINA_TRUE; + + ewk_tile_matrix_free(priv->model.matrix); + } + + priv->model.matrix = ewk_tile_matrix_new + (tuc, priv->model.cur.cols, priv->model.cur.rows, priv->cspace, + _ewk_tiled_backing_store_render, priv); +} + +static void _ewk_tiled_backing_store_smart_member_del(Evas_Object *o, Evas_Object *member) +{ + PRIV_DATA_GET_OR_RETURN(o, priv); + if (!priv->contents_clipper) + return; + evas_object_clip_unset(member); + if (!evas_object_clipees_get(priv->contents_clipper)) + evas_object_hide(priv->contents_clipper); +} + +static void _ewk_tiled_backing_store_smart_member_add(Evas_Object *o, Evas_Object *member) +{ + PRIV_DATA_GET_OR_RETURN(o, priv); + if (!priv->contents_clipper) + return; + evas_object_clip_set(member, priv->contents_clipper); + if (evas_object_visible_get(o)) + evas_object_show(priv->contents_clipper); +} + +#ifdef DEBUG_MEM_LEAKS +static void _ewk_tiled_backing_store_mem_dbg(Ewk_Tiled_Backing_Store_Data *priv) +{ + static int run = 0; + + run++; + + printf("\n--- BEGIN DEBUG TILED BACKING STORE MEMORY [%d] --\n" + "t=%0.2f, obj=%p, priv=%p, view.items=%p, matrix=%p\n", + run, ecore_loop_time_get(), + priv->self, priv, priv->view.items, priv->model.matrix); + + ewk_tile_matrix_dbg(priv->model.matrix); + ewk_tile_accounting_dbg(); + + printf("--- END DEBUG TILED BACKING STORE MEMORY [%d] --\n\n", run); +} + +static Eina_Bool _ewk_tiled_backing_store_sig_usr(void *data, int type, void *event) +{ + Ecore_Event_Signal_User *sig = (Ecore_Event_Signal_User*)event; + Ewk_Tiled_Backing_Store_Data *priv = (Ewk_Tiled_Backing_Store_Data*)data; + + if (sig->number == 2) { + Ewk_Tile_Unused_Cache *tuc; + tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix); + ewk_tile_unused_cache_auto_flush(tuc); + } + + _ewk_tiled_backing_store_view_dbg(priv); + _ewk_tiled_backing_store_mem_dbg(priv); + return EINA_TRUE; +} +#endif + +static void _ewk_tiled_backing_store_smart_add(Evas_Object *o) +{ + Ewk_Tiled_Backing_Store_Data *priv; + + DBG("o=%p", o); + + CALLOC_OR_OOM_RET(priv, sizeof(*priv)); + + priv->self = o; + priv->view.tile.zoom = 1.0; + priv->view.tile.w = TILE_W; + priv->view.tile.h = TILE_H; + priv->view.offset.cur.x = 0; + priv->view.offset.cur.y = 0; + priv->view.offset.old.x = 0; + priv->view.offset.old.y = 0; + priv->view.offset.base.x = -TILE_W; + priv->view.offset.base.y = -TILE_H; + + priv->model.base.col = -1; + priv->model.base.row = -1; + priv->model.cur.cols = 1; + priv->model.cur.rows = 1; + priv->model.old.cols = 0; + priv->model.old.rows = 0; + priv->model.width = 0; + priv->model.height = 0; + priv->render.process_entire_queue = EINA_TRUE; + priv->render.suspend = EINA_FALSE; + priv->cspace = EVAS_COLORSPACE_ARGB8888; // TODO: detect it. + + evas_object_smart_data_set(o, priv); + _parent_sc.add(o); + + priv->contents_clipper = evas_object_rectangle_add( + evas_object_evas_get(o)); + evas_object_move(priv->contents_clipper, 0, 0); + evas_object_resize(priv->contents_clipper, + priv->model.width, priv->model.height); + evas_object_color_set(priv->contents_clipper, 255, 255, 255, 255); + evas_object_show(priv->contents_clipper); + evas_object_smart_member_add(priv->contents_clipper, o); + + _ewk_tiled_backing_store_model_matrix_create(priv, NULL); + evas_object_move(priv->base.clipper, 0, 0); + evas_object_resize(priv->base.clipper, 0, 0); + evas_object_clip_set(priv->contents_clipper, priv->base.clipper); + +#ifdef DEBUG_MEM_LEAKS + priv->sig_usr = ecore_event_handler_add + (ECORE_EVENT_SIGNAL_USER, _ewk_tiled_backing_store_sig_usr, priv); +#endif +} + +static void _ewk_tiled_backing_store_smart_del(Evas_Object *o) +{ + PRIV_DATA_GET_OR_RETURN(o, priv); + DBG("o=%p", o); + Ewk_Tile_Unused_Cache *tuc; + + tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix); + ewk_tile_unused_cache_unlock_area(tuc); + + _ewk_tiled_backing_store_flush(priv); + + _ewk_tiled_backing_store_pre_render_request_flush(priv); + _ewk_tiled_backing_store_item_process_idler_stop(priv); + _ewk_tiled_backing_store_view_rows_all_del(priv); + +#ifdef DEBUG_MEM_LEAKS + _ewk_tiled_backing_store_mem_dbg(priv); + if (priv->sig_usr) + priv->sig_usr = ecore_event_handler_del(priv->sig_usr); +#endif + + ewk_tile_matrix_free(priv->model.matrix); + evas_object_smart_member_del(priv->contents_clipper); + evas_object_del(priv->contents_clipper); + + _parent_sc.del(o); + +#ifdef DEBUG_MEM_LEAKS + printf("\nIMPORTANT: TILED BACKING STORE DELETED (may be real leaks)\n"); + ewk_tile_accounting_dbg(); +#endif +} + +static void _ewk_tiled_backing_store_smart_move(Evas_Object *o, Evas_Coord x, Evas_Coord y) +{ + DBG("o=%p, new pos: %dx%d", o, x, y); + + PRIV_DATA_GET_OR_RETURN(o, priv); + + if (priv->changed.pos) + return; + + if (priv->view.x == x && priv->view.y == y) + return; + + priv->changed.pos = EINA_TRUE; + _ewk_tiled_backing_store_changed(priv); +} + +static void _ewk_tiled_backing_store_smart_resize(Evas_Object *o, Evas_Coord w, Evas_Coord h) +{ + DBG("o=%p, new size: %dx%d", o, w, h); + + PRIV_DATA_GET_OR_RETURN(o, priv); + + if (priv->changed.size) + return; + + if (priv->view.w == w && priv->view.h == h) + return; + + priv->changed.size = EINA_TRUE; + _ewk_tiled_backing_store_changed(priv); +} + +static void _ewk_tiled_backing_store_recalc_renderers(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord w, Evas_Coord h, Evas_Coord tw, Evas_Coord th) +{ + long cols, rows, old_rows, old_cols; + INF("o=%p, new size: %dx%d", priv->self, w, h); + + cols = 2 + (int)ceil((float)w / (float)tw); + rows = 2 + (int)ceil((float)h / (float)th); + + INF("o=%p new grid size cols: %ld, rows: %ld, was %ld, %ld", + priv->self, cols, rows, priv->view.cols, priv->view.rows); + + if (priv->view.cols == cols && priv->view.rows == rows) + return; + + _ewk_tiled_backing_store_pre_render_request_remove_associated(priv); + + old_cols = priv->view.cols; + old_rows = priv->view.rows; + + if (rows < old_rows) { + Eina_Inlist **start, **end; + start = priv->view.items + rows; + end = priv->view.items + old_rows; + _ewk_tiled_backing_store_view_rows_range_del(priv, start, end); + } + REALLOC_OR_OOM_RET(priv->view.items, sizeof(Eina_Inlist*) * (int)rows); + priv->view.rows = rows; + priv->view.cols = cols; + if (rows > old_rows) { + Eina_Inlist **start, **end; + start = priv->view.items + old_rows; + end = priv->view.items + rows; + for (; start < end; start++) { + Eina_Bool r; + *start = NULL; + r = _ewk_tiled_backing_store_view_cols_end_add + (priv, start, 0, cols); + if (!r) { + CRITICAL("failed to allocate %ld columns", cols); + _ewk_tiled_backing_store_view_rows_range_del + (priv, priv->view.items + old_rows, start); + priv->view.rows = old_rows; + return; + } + } + } + + if (cols != old_cols) { + Eina_Inlist **start, **end; + int todo = cols - old_cols; + start = priv->view.items; + end = start + MIN(old_rows, rows); + if (todo > 0) { + for (; start < end; start++) { + Eina_Bool r; + r = _ewk_tiled_backing_store_view_cols_end_add + (priv, start, old_cols, todo); + if (!r) { + CRITICAL("failed to allocate %d columns!", todo); + + for (start--; start >= priv->view.items; start--) + _ewk_tiled_backing_store_view_cols_end_del(priv, start, todo); + if (rows > old_rows) { + start = priv->view.items + old_rows; + end = priv->view.items + rows; + for (; start < end; start++) + _ewk_tiled_backing_store_view_cols_end_del(priv, start, todo); + } + return; + } + } + } else if (todo < 0) { + todo = -todo; + for (; start < end; start++) + _ewk_tiled_backing_store_view_cols_end_del(priv, start, todo); + } + } + + _ewk_tiled_backing_store_fill_renderers(priv); +} + +static void _ewk_tiled_backing_store_smart_calculate_size(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord w, Evas_Coord h) +{ + evas_object_resize(priv->base.clipper, w, h); + + priv->view.w = w; + priv->view.h = h; + + _ewk_tiled_backing_store_recalc_renderers( + priv, w, h, priv->view.tile.w, priv->view.tile.h); +} + +// TODO: remove me later. +static inline void _ewk_tiled_backing_store_view_dbg(const Ewk_Tiled_Backing_Store_Data *priv) +{ + Eina_Inlist **start, **end; + printf("tiles=%2ld,%2ld model=%2ld,%2ld [%dx%d] base=%+3ld,%+4ld offset=%+4d,%+4d old=%+4d,%+4d base=%+3d,%+3d\n", + priv->view.cols, priv->view.rows, + priv->model.cur.cols, priv->model.cur.rows, + priv->model.width, priv->model.height, + priv->model.base.col, priv->model.base.row, + priv->view.offset.cur.x, priv->view.offset.cur.y, + priv->view.offset.old.x, priv->view.offset.old.y, + priv->view.offset.base.x, priv->view.offset.base.y); + + start = priv->view.items; + end = priv->view.items + priv->view.rows; + for (; start < end; start++) { + const Ewk_Tiled_Backing_Store_Item *it; + + EINA_INLIST_FOREACH(*start, it) { + printf(" %+4d,%+4d ", it->geometry.x, it->geometry.y); + + if (!it->tile) + printf(" ;"); + else + printf("%8p %lu,%lu;", it->tile, it->tile->col, it->tile->row); + } + printf("\n"); + } + printf("---\n"); +} + +/** + * @internal + * Move top row down as last. + * + * The final result is visually the same, but logically the top that + * went out of screen is now at bottom and filled with new model items. + * + * This is worth just when @a count is smaller than @c + * priv->view.rows, after that one is refilling the whole matrix so it + * is better to trigger full refill. + * + * @param count the number of times to repeat the process. + */ +static void _ewk_tiled_backing_store_view_wrap_up(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y, unsigned int count) +{ + unsigned int last_row = priv->view.rows - 1; + Evas_Coord tw = priv->view.tile.w; + Evas_Coord th = priv->view.tile.h; + Evas_Coord off_y = (priv->view.offset.base.y % th) - th; + Evas_Coord oy = y + (last_row - count + 1) * th + off_y; + Eina_Inlist **itr_start, **itr_end; + + itr_start = priv->view.items; + itr_end = itr_start + last_row; + + for (; count > 0; count--) { + Eina_Inlist **itr; + Eina_Inlist *tmp = *itr_start; + Ewk_Tiled_Backing_Store_Item *it; + Evas_Coord ox = x + priv->view.offset.base.x; + int c = 0; + + for (itr = itr_start; itr < itr_end; itr++) + *itr = *(itr + 1); + *itr = tmp; + + priv->model.base.row++; + EINA_INLIST_FOREACH(tmp, it) { + _ewk_tiled_backing_store_item_move(it, ox, oy); + ox += tw; + _ewk_tiled_backing_store_item_fill(priv, it, c, last_row); + c++; + } + oy += th; + } + priv->view.offset.base.y = off_y; +} + +/** + * @internal + * Move bottom row up as first. + * + * The final result is visually the same, but logically the bottom that + * went out of screen is now at top and filled with new model items. + * + * This is worth just when @a count is smaller than @c + * priv->view.rows, after that one is refilling the whole matrix so it + * is better to trigger full refill. + * + * @param count the number of times to repeat the process. + */ +static void _ewk_tiled_backing_store_view_wrap_down(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y, unsigned int count) +{ + Evas_Coord tw = priv->view.tile.w; + Evas_Coord th = priv->view.tile.h; + Evas_Coord off_y = (priv->view.offset.base.y % th) - th; + Evas_Coord oy = y + off_y + (count - 1) * th; + Eina_Inlist **itr_start, **itr_end; + + itr_start = priv->view.items + priv->view.rows - 1; + itr_end = priv->view.items; + + for (; count > 0; count--) { + Eina_Inlist **itr; + Eina_Inlist *tmp = *itr_start; + Ewk_Tiled_Backing_Store_Item *it; + Evas_Coord ox = x + priv->view.offset.base.x; + int c = 0; + + for (itr = itr_start; itr > itr_end; itr--) + *itr = *(itr - 1); + *itr = tmp; + + priv->model.base.row--; + EINA_INLIST_FOREACH(tmp, it) { + _ewk_tiled_backing_store_item_move(it, ox, oy); + ox += tw; + _ewk_tiled_backing_store_item_fill(priv, it, c, 0); + c++; + } + oy -= th; + } + priv->view.offset.base.y = off_y; +} + +/** + * @internal + * Move left-most (first) column right as last (right-most). + * + * The final result is visually the same, but logically the first col that + * went out of screen is now at last and filled with new model items. + * + * This is worth just when @a count is smaller than @c + * priv->view.cols, after that one is refilling the whole matrix so it + * is better to trigger full refill. + * + * @param count the number of times to repeat the process. + */ +static void _ewk_tiled_backing_store_view_wrap_left(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y, unsigned int count) +{ + unsigned int r, last_col = priv->view.cols - 1; + Evas_Coord tw = priv->view.tile.w; + Evas_Coord th = priv->view.tile.h; + Evas_Coord off_x = (priv->view.offset.base.x % tw) - tw; + Evas_Coord oy = y + priv->view.offset.base.y; + Eina_Inlist **itr; + Eina_Inlist **itr_end; + + itr = priv->view.items; + itr_end = itr + priv->view.rows; + r = 0; + + priv->model.base.col += count; + + for (; itr < itr_end; itr++, r++) { + Evas_Coord ox = x + (last_col - count + 1) * tw + off_x; + unsigned int i, c = last_col - count + 1; + + for (i = 0; i < count; i++, c++, ox += tw) { + Ewk_Tiled_Backing_Store_Item *it; + it = EINA_INLIST_CONTAINER_GET(*itr, Ewk_Tiled_Backing_Store_Item); + *itr = eina_inlist_demote(*itr, *itr); + + _ewk_tiled_backing_store_item_move(it, ox, oy); + _ewk_tiled_backing_store_item_fill(priv, it, c, r); + } + oy += th; + } + + priv->view.offset.base.x = off_x; +} + +/** + * @internal + * Move right-most (last) column left as first (left-most). + * + * The final result is visually the same, but logically the last col that + * went out of screen is now at first and filled with new model items. + * + * This is worth just when @a count is smaller than @c + * priv->view.cols, after that one is refilling the whole matrix so it + * is better to trigger full refill. + * + * @param count the number of times to repeat the process. + */ +static void _ewk_tiled_backing_store_view_wrap_right(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y, unsigned int count) +{ + unsigned int r; + Evas_Coord tw = priv->view.tile.w; + Evas_Coord th = priv->view.tile.h; + Evas_Coord off_x = (priv->view.offset.base.x % tw) - tw; + Evas_Coord oy = y + priv->view.offset.base.y; + Eina_Inlist **itr, **itr_end; + + itr = priv->view.items; + itr_end = itr + priv->view.rows; + r = 0; + + priv->model.base.col -= count; + + for (; itr < itr_end; itr++, r++) { + Evas_Coord ox = x + (count - 1) * tw + off_x; + unsigned int i, c = count - 1; + + for (i = 0; i < count; i++, c--, ox -= tw) { + Ewk_Tiled_Backing_Store_Item *it; + it = EINA_INLIST_CONTAINER_GET((*itr)->last, Ewk_Tiled_Backing_Store_Item); + *itr = eina_inlist_promote(*itr, (*itr)->last); + + _ewk_tiled_backing_store_item_move(it, ox, oy); + _ewk_tiled_backing_store_item_fill(priv, it, c, r); + } + oy += th; + } + + priv->view.offset.base.x = off_x; +} + +static void _ewk_tiled_backing_store_view_refill(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y, int step_x, int step_y) +{ + Eina_Inlist **itr, **itr_end; + Evas_Coord base_ox, oy, tw, th; + unsigned int r; + + evas_object_move(priv->base.clipper, x, y); + + tw = priv->view.tile.w; + th = priv->view.tile.h; + + base_ox = x + priv->view.offset.base.x; + oy = y + priv->view.offset.base.y; + + itr = priv->view.items; + itr_end = itr + priv->view.rows; + r = 0; + + priv->model.base.col -= step_x; + priv->model.base.row -= step_y; + + for (; itr < itr_end; itr++, r++) { + Ewk_Tiled_Backing_Store_Item *it; + Evas_Coord ox = base_ox; + unsigned int c = 0; + EINA_INLIST_FOREACH(*itr, it) { + _ewk_tiled_backing_store_item_fill(priv, it, c, r); + _ewk_tiled_backing_store_item_move(it, ox, oy); + c++; + ox += tw; + } + oy += th; + } +} + +static void _ewk_tiled_backing_store_view_pos_apply(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y) +{ + Eina_Inlist **itr, **itr_end; + Evas_Coord base_ox, oy, tw, th; + + evas_object_move(priv->base.clipper, x, y); + + tw = priv->view.tile.w; + th = priv->view.tile.h; + + base_ox = x + priv->view.offset.base.x; + oy = y + priv->view.offset.base.y; + + itr = priv->view.items; + itr_end = itr + priv->view.rows; + for (; itr < itr_end; itr++) { + Ewk_Tiled_Backing_Store_Item *it; + Evas_Coord ox = base_ox; + EINA_INLIST_FOREACH(*itr, it) { + _ewk_tiled_backing_store_item_move(it, ox, oy); + ox += tw; + } + oy += th; + } +} + +static void _ewk_tiled_backing_store_smart_calculate_offset_force(Ewk_Tiled_Backing_Store_Data *priv) +{ + Evas_Coord dx = priv->view.offset.cur.x - priv->view.offset.old.x; + Evas_Coord dy = priv->view.offset.cur.y - priv->view.offset.old.y; + Evas_Coord tw, th; + int step_y, step_x; + + INF("o=%p, offset: %+4d, %+4d (%+4d, %+4d)", + priv->self, dx, dy, priv->view.offset.cur.x, priv->view.offset.cur.y); + + tw = priv->view.tile.w; + th = priv->view.tile.h; + + step_x = (dx + priv->view.offset.base.x + tw) / tw; + step_y = (dy + priv->view.offset.base.y + th) / th; + + priv->view.offset.old.x = priv->view.offset.cur.x; + priv->view.offset.old.y = priv->view.offset.cur.y; + evas_object_move( + priv->contents_clipper, + priv->view.offset.cur.x + priv->view.x, + priv->view.offset.cur.y + priv->view.y); + + priv->view.offset.base.x += dx - step_x * tw; + priv->view.offset.base.y += dy - step_y * th; + + _ewk_tiled_backing_store_view_refill + (priv, priv->view.x, priv->view.y, step_x, step_y); +} + +static void _ewk_tiled_backing_store_smart_calculate_offset(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y) +{ + Evas_Coord dx = priv->view.offset.cur.x - priv->view.offset.old.x; + Evas_Coord dy = priv->view.offset.cur.y - priv->view.offset.old.y; + Evas_Coord tw, th; + int step_y, step_x; + + INF("o=%p, offset: %+4d, %+4d (%+4d, %+4d)", + priv->self, dx, dy, priv->view.offset.cur.x, priv->view.offset.cur.y); + + if (!dx && !dy) + return; + + tw = priv->view.tile.w; + th = priv->view.tile.h; + + step_x = (dx + priv->view.offset.base.x + tw) / tw; + step_y = (dy + priv->view.offset.base.y + th) / th; + + priv->view.offset.old.x = priv->view.offset.cur.x; + priv->view.offset.old.y = priv->view.offset.cur.y; + evas_object_move( + priv->contents_clipper, + priv->view.offset.cur.x + priv->view.x, + priv->view.offset.cur.y + priv->view.y); + + if ((step_x < 0 && step_x <= -priv->view.cols) + || (step_x > 0 && step_x >= priv->view.cols) + || (step_y < 0 && step_y <= -priv->view.rows) + || (step_y > 0 && step_y >= priv->view.rows)) { + + priv->view.offset.base.x += dx - step_x * tw; + priv->view.offset.base.y += dy - step_y * th; + + _ewk_tiled_backing_store_view_refill + (priv, priv->view.x, priv->view.y, step_x, step_y); + return; + } + + priv->view.offset.base.x += dx; + priv->view.offset.base.y += dy; + + if (step_y < 0) + _ewk_tiled_backing_store_view_wrap_up(priv, x, y, -step_y); + else if (step_y > 0) + _ewk_tiled_backing_store_view_wrap_down(priv, x, y, step_y); + + if (step_x < 0) + _ewk_tiled_backing_store_view_wrap_left(priv, x, y, -step_x); + else if (step_x > 0) + _ewk_tiled_backing_store_view_wrap_right(priv, x, y, step_x); + + _ewk_tiled_backing_store_view_pos_apply(priv, x, y); +} + +static void _ewk_tiled_backing_store_smart_calculate_pos(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y) +{ + _ewk_tiled_backing_store_view_pos_apply(priv, x, y); + priv->view.x = x; + priv->view.y = y; + evas_object_move( + priv->contents_clipper, + priv->view.offset.cur.x + priv->view.x, + priv->view.offset.cur.y + priv->view.y); +} + +static void _ewk_tiled_backing_store_fill_renderers(Ewk_Tiled_Backing_Store_Data *priv) +{ + Eina_Inlist *it; + Ewk_Tiled_Backing_Store_Item *item; + int i, j; + + for (i = 0; i < priv->view.rows; i++) { + it = priv->view.items[i]; + j = 0; + EINA_INLIST_FOREACH(it, item) + _ewk_tiled_backing_store_item_fill(priv, item, j++, i); + } +} + +static void _ewk_tiled_backing_store_smart_calculate(Evas_Object *o) +{ + Evas_Coord x, y, w, h; + + evas_object_geometry_get(o, &x, &y, &w, &h); + DBG("o=%p at %d,%d + %dx%d", o, x, y, w, h); + + PRIV_DATA_GET_OR_RETURN(o, priv); + + priv->changed.any = EINA_FALSE; + + ewk_tile_matrix_freeze(priv->model.matrix); + + if (!priv->render.suspend && priv->changed.model) { + long cols, rows; + + cols = priv->model.width / priv->view.tile.w + 1; + rows = priv->model.height / priv->view.tile.h + 1; + + priv->model.old.cols = priv->model.cur.cols; + priv->model.old.rows = priv->model.cur.rows; + priv->model.cur.cols = cols; + priv->model.cur.rows = rows; + if (priv->model.old.cols > cols) + cols = priv->model.old.cols; + if (priv->model.old.rows > rows) + rows = priv->model.old.rows; + ewk_tile_matrix_resize(priv->model.matrix, cols, rows); + } + + if (priv->changed.pos && (priv->view.x != x || priv->view.y != y)) { + _ewk_tiled_backing_store_smart_calculate_pos(priv, x, y); + priv->changed.pos = EINA_FALSE; + } else if (priv->changed.offset) { + _ewk_tiled_backing_store_smart_calculate_offset(priv, x, y); + priv->changed.offset = EINA_FALSE; + } + + if (priv->changed.size) { + _ewk_tiled_backing_store_smart_calculate_size(priv, w, h); + priv->changed.size = EINA_FALSE; + } + + if (!priv->render.suspend && priv->changed.model) { + Eina_Rectangle rect; + rect.x = 0; + rect.y = 0; + rect.w = priv->model.width; + rect.h = priv->model.height; + _ewk_tiled_backing_store_fill_renderers(priv); + ewk_tile_matrix_resize(priv->model.matrix, + priv->model.cur.cols, + priv->model.cur.rows); + priv->changed.model = EINA_FALSE; + evas_object_resize(priv->contents_clipper, + priv->model.width, priv->model.height); + _ewk_tiled_backing_store_smart_calculate_offset_force(priv); + + /* Make sure we do not miss any important repaint by + * repainting the whole viewport */ + const Eina_Rectangle r = + { 0, 0, priv->model.width, priv->model.height }; + ewk_tile_matrix_update(priv->model.matrix, &r, + priv->view.tile.zoom); + } + + ewk_tile_matrix_thaw(priv->model.matrix); + + _ewk_tiled_backing_store_updates_process(priv); + + if (priv->view.offset.base.x >= 0 + || priv->view.offset.base.x <= -2 * priv->view.tile.w + || priv->view.offset.base.y >= 0 + || priv->view.offset.base.y <= -2 * priv->view.tile.h) + ERR("incorrect base offset %+4d,%+4d, tile=%dx%d, cur=%+4d,%+4d\n", + priv->view.offset.base.x, priv->view.offset.base.y, + priv->view.tile.w, priv->view.tile.h, + priv->view.offset.cur.x, priv->view.offset.cur.y); + +} + +Evas_Object *ewk_tiled_backing_store_add(Evas *e) +{ + static Evas_Smart *smart = NULL; + + if (_ewk_tiled_log_dom < 0) + _ewk_tiled_log_dom = eina_log_domain_register("Ewk_Tiled_Backing_Store", NULL); + + if (!smart) { + static Evas_Smart_Class sc = + EVAS_SMART_CLASS_INIT_NAME_VERSION("Ewk_Tiled_Backing_Store"); + + evas_object_smart_clipped_smart_set(&sc); + _parent_sc = sc; + + sc.add = _ewk_tiled_backing_store_smart_add; + sc.del = _ewk_tiled_backing_store_smart_del; + sc.resize = _ewk_tiled_backing_store_smart_resize; + sc.move = _ewk_tiled_backing_store_smart_move; + sc.calculate = _ewk_tiled_backing_store_smart_calculate; + sc.member_add = _ewk_tiled_backing_store_smart_member_add; + sc.member_del = _ewk_tiled_backing_store_smart_member_del; + + smart = evas_smart_class_new(&sc); + } + + return evas_object_smart_add(e, smart); +} + +void ewk_tiled_backing_store_render_cb_set(Evas_Object *o, Eina_Bool (*cb)(void *data, Ewk_Tile *t, const Eina_Rectangle *area), const void *data) +{ + EINA_SAFETY_ON_NULL_RETURN(cb); + PRIV_DATA_GET_OR_RETURN(o, priv); + priv->render.cb = cb; + priv->render.data = (void*)data; +} + +Ewk_Tile_Unused_Cache *ewk_tiled_backing_store_tile_unused_cache_get(const Evas_Object *o) +{ + PRIV_DATA_GET_OR_RETURN(o, priv, NULL); + return ewk_tile_matrix_unused_cache_get(priv->model.matrix); +} + +void ewk_tiled_backing_store_tile_unused_cache_set(Evas_Object *o, Ewk_Tile_Unused_Cache *tuc) +{ + PRIV_DATA_GET_OR_RETURN(o, priv); + + if (ewk_tile_matrix_unused_cache_get(priv->model.matrix) == tuc) + return; + + _ewk_tiled_backing_store_model_matrix_create(priv, tuc); +} + +static Eina_Bool _ewk_tiled_backing_store_scroll_full_offset_set_internal(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y) +{ + /* TODO: check offset go out of bounds, clamp */ + if (priv->render.disabled) + return EINA_FALSE; + + priv->view.offset.cur.x = x; + priv->view.offset.cur.y = y; + + priv->changed.offset = EINA_TRUE; + _ewk_tiled_backing_store_changed(priv); + + return EINA_TRUE; +} + +Eina_Bool ewk_tiled_backing_store_scroll_full_offset_set(Evas_Object *o, Evas_Coord x, Evas_Coord y) +{ + DBG("o=%p, x=%d, y=%d", o, x, y); + + PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE); + if (x == priv->view.offset.cur.x && y == priv->view.offset.cur.y) + return EINA_TRUE; + + return _ewk_tiled_backing_store_scroll_full_offset_set_internal(priv, x, y); +} + +Eina_Bool ewk_tiled_backing_store_scroll_full_offset_add(Evas_Object *o, Evas_Coord dx, Evas_Coord dy) +{ + DBG("o=%p, dx=%d, dy=%d", o, dx, dy); + + PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE); + if (!dx && !dy) + return EINA_TRUE; + + return _ewk_tiled_backing_store_scroll_full_offset_set_internal + (priv, priv->view.offset.cur.x + dx, priv->view.offset.cur.y + dy); +} + +static Eina_Bool _ewk_tiled_backing_store_zoom_set_internal(Ewk_Tiled_Backing_Store_Data *priv, float *zoom, Evas_Coord cx, Evas_Coord cy, Evas_Coord *offx, Evas_Coord *offy) +{ + *offx = priv->view.offset.cur.x; + *offy = priv->view.offset.cur.y; + + if (fabsf(priv->view.tile.zoom - *zoom) < ZOOM_STEP_MIN) { + DBG("ignored as zoom difference is < %f: %f", + (double)ZOOM_STEP_MIN, fabsf(priv->view.tile.zoom - *zoom)); + return EINA_TRUE; + } + + _ewk_tiled_backing_store_pre_render_request_flush(priv); + Evas_Coord tw, th; + tw = TILE_SIZE_AT_ZOOM(TILE_W, *zoom); + tw = (tw >> 1) << 1; + *zoom = TILE_W_ZOOM_AT_SIZE(tw); + /* WARNING: assume reverse zoom is the same for both axis */ + th = TILE_SIZE_AT_ZOOM(TILE_H, *zoom); + + float scale = *zoom / priv->view.tile.zoom; + + priv->view.tile.zoom = *zoom; + // todo: check cx [0, w]... + priv->view.offset.zoom_center.x = cx; + priv->view.offset.zoom_center.y = cy; + + priv->view.tile.w = tw; + priv->view.tile.h = th; + + if (!priv->view.w || !priv->view.h) { + priv->view.offset.base.x = -tw; + priv->view.offset.base.y = -th; + return EINA_TRUE; + } + Eina_Inlist **itr, **itr_end; + Ewk_Tiled_Backing_Store_Item *it; + + Evas_Coord new_x = cx + (priv->view.offset.cur.x - cx) * scale; + Evas_Coord new_y = cy + (priv->view.offset.cur.y - cy) * scale; + Evas_Coord bx = cx + (priv->view.offset.base.x - cx) * scale; + Evas_Coord by = cy + (priv->view.offset.base.y - cy) * scale; + + Evas_Coord model_width = priv->model.width * scale; + Evas_Coord model_height = priv->model.height * scale; + + if (model_width < priv->view.w || new_x >= 0) + new_x = 0; + else if (-new_x + priv->view.w >= model_width) + new_x = -model_width + priv->view.w; + + if (model_height < priv->view.h || new_y >= 0) + new_y = 0; + else if (-new_y + priv->view.h >= model_height) + new_y = -model_height + priv->view.h; + + bx = new_x % tw - tw; + priv->model.base.col = - new_x / tw - 1; + by = new_y % th - th; + priv->model.base.row = - new_y / th - 1; + + priv->changed.size = EINA_TRUE; + _ewk_tiled_backing_store_changed(priv); + + priv->view.offset.cur.x = new_x; + priv->view.offset.cur.y = new_y; + priv->view.offset.base.x = bx; + priv->view.offset.base.y = by; + + priv->view.offset.old.x = priv->view.offset.cur.x; + priv->view.offset.old.y = priv->view.offset.cur.y; + *offx = priv->view.offset.cur.x; + *offy = priv->view.offset.cur.y; + + evas_object_move( + priv->contents_clipper, + new_x + priv->view.x, + new_y + priv->view.y); + + _ewk_tiled_backing_store_fill_renderers(priv); + + Evas_Coord oy = priv->view.offset.base.y + priv->view.y; + Evas_Coord base_ox = priv->view.x + priv->view.offset.base.x; + + itr = priv->view.items; + itr_end = itr + priv->view.rows; + + for (; itr < itr_end; itr++) { + Evas_Coord ox = base_ox; + Eina_Inlist *lst = *itr; + + EINA_INLIST_FOREACH(lst, it) { + _ewk_tiled_backing_store_item_move(it, ox, oy); + _ewk_tiled_backing_store_item_resize(it, tw, th); + ox += tw; + } + oy += th; + } + + return EINA_TRUE; +} + +Eina_Bool ewk_tiled_backing_store_zoom_set(Evas_Object *o, float *zoom, Evas_Coord cx, Evas_Coord cy, Evas_Coord *offx, Evas_Coord *offy) +{ + DBG("o=%p, zoom=%f", o, (double)*zoom); + + PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE); + + return _ewk_tiled_backing_store_zoom_set_internal(priv, zoom, cx, cy, offx, offy); +} + +Eina_Bool ewk_tiled_backing_store_zoom_weak_set(Evas_Object *o, float zoom, Evas_Coord cx, Evas_Coord cy) +{ + DBG("o=%p, zoom=%f", o, (double)zoom); + PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE); + if (!priv->view.w || !priv->view.h) + return EINA_FALSE; + Eina_Inlist **itr, **itr_end; + Ewk_Tiled_Backing_Store_Item *it; + Evas_Coord tw, th; + Eina_Bool recalc = EINA_FALSE; + + tw = TILE_SIZE_AT_ZOOM(TILE_W, zoom); + zoom = TILE_W_ZOOM_AT_SIZE(tw); + /* WARNING: assume reverse zoom is the same for both axis */ + th = TILE_SIZE_AT_ZOOM(TILE_H, zoom); + + float scale = zoom / priv->view.tile.zoom; + + Evas_Coord model_width = priv->model.width * scale; + Evas_Coord model_height = priv->model.height * scale; + + evas_object_resize(priv->contents_clipper, + model_width, model_height); + + int vrows = ceil((float)priv->view.h / (float)th) + 2; + int vcols = ceil((float)priv->view.w / (float)tw) + 2; + Evas_Coord new_x = cx + (priv->view.offset.cur.x - cx) * scale; + Evas_Coord new_y = cy + (priv->view.offset.cur.y - cy) * scale; + Evas_Coord bx = new_x % tw - tw; + Evas_Coord by = new_y % th - th; + unsigned long base_row = -new_y / th - 1; + unsigned long base_col = -new_x / tw - 1; + + if (base_row != priv->model.base.row || base_col != priv->model.base.col) { + priv->model.base.row = base_row; + priv->model.base.col = base_col; + recalc = EINA_TRUE; + } + + if (vrows > priv->view.rows || vcols > priv->view.cols) + recalc = EINA_TRUE; + + if (recalc) { + Evas_Coord w, h; + evas_object_geometry_get(o, NULL, NULL, &w, &h); + _ewk_tiled_backing_store_recalc_renderers(priv, w, h, tw, th); + _ewk_tiled_backing_store_fill_renderers(priv); + _ewk_tiled_backing_store_updates_process(priv); + } + + Evas_Coord base_ox = bx + priv->view.x; + Evas_Coord oy = by + priv->view.y; + + evas_object_move(priv->contents_clipper, + new_x + priv->view.x, + new_y + priv->view.y); + + itr = priv->view.items; + itr_end = itr + priv->view.rows; + + for (; itr < itr_end; itr++) { + Evas_Coord ox = base_ox; + Eina_Inlist *lst = *itr; + + EINA_INLIST_FOREACH(lst, it) { + _ewk_tiled_backing_store_item_move(it, ox, oy); + _ewk_tiled_backing_store_item_resize(it, tw, th); + ox += tw; + } + oy += th; + } + + return EINA_TRUE; +} + +void ewk_tiled_backing_store_fix_offsets(Evas_Object *o, Evas_Coord w, Evas_Coord h) +{ + PRIV_DATA_GET_OR_RETURN(o, priv); + Eina_Inlist **itr, **itr_end; + Ewk_Tiled_Backing_Store_Item *it; + Evas_Coord new_x = priv->view.offset.cur.x; + Evas_Coord new_y = priv->view.offset.cur.y; + Evas_Coord bx = priv->view.offset.base.x; + Evas_Coord by = priv->view.offset.base.y; + Evas_Coord tw = priv->view.tile.w; + Evas_Coord th = priv->view.tile.h; + + if (-new_x > w) { + new_x = -w; + bx = new_x % tw - tw; + priv->model.base.col = -new_x / tw - 1; + } + + if (-new_y > h) { + new_y = -h; + by = new_y % th - th; + priv->model.base.row = -new_y / th - 1; + } + + if (bx >= 0 || bx <= -2 * priv->view.tile.w) { + bx = new_x % tw - tw; + priv->model.base.col = -new_x / tw - 1; + } + + if (by >= 0 || by <= -2 * priv->view.tile.h) { + by = new_y % th - th; + priv->model.base.row = -new_y / th - 1; + } + + priv->view.offset.cur.x = new_x; + priv->view.offset.cur.y = new_y; + priv->view.offset.old.x = new_x; + priv->view.offset.old.y = new_y; + priv->view.offset.base.x = bx; + priv->view.offset.base.y = by; + evas_object_move(priv->contents_clipper, + new_x + priv->view.x, + new_y + priv->view.y); + + Evas_Coord oy = priv->view.offset.base.y + priv->view.y; + Evas_Coord base_ox = priv->view.x + priv->view.offset.base.x; + + itr = priv->view.items; + itr_end = itr + priv->view.rows; + + for (; itr < itr_end; itr++) { + Evas_Coord ox = base_ox; + Eina_Inlist *lst = *itr; + + EINA_INLIST_FOREACH(lst, it) { + _ewk_tiled_backing_store_item_move(it, ox, oy); + _ewk_tiled_backing_store_item_resize(it, tw, th); + ox += tw; + } + oy += th; + } +} + +void ewk_tiled_backing_store_zoom_weak_smooth_scale_set(Evas_Object *o, Eina_Bool smooth_scale) +{ + PRIV_DATA_GET_OR_RETURN(o, priv); + Eina_Inlist **itr, **itr_end; + + itr = priv->view.items; + itr_end = itr + priv->view.rows; + priv->view.tile.zoom_weak_smooth_scale = smooth_scale; + + for (; itr< itr_end; itr++) { + Ewk_Tiled_Backing_Store_Item *it; + EINA_INLIST_FOREACH(*itr, it) + if (it->tile) + _ewk_tiled_backing_store_item_smooth_scale_set + (it, smooth_scale); + } +} + +Eina_Bool ewk_tiled_backing_store_update(Evas_Object *o, const Eina_Rectangle *update) +{ + PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE); + + if (priv->render.disabled) + return EINA_FALSE; + + return ewk_tile_matrix_update(priv->model.matrix, update, + priv->view.tile.zoom); +} + +void ewk_tiled_backing_store_updates_process_pre_set(Evas_Object *o, void *(*cb)(void *data, Evas_Object *o), const void *data) +{ + PRIV_DATA_GET_OR_RETURN(o, priv); + priv->process.pre_cb = cb; + priv->process.pre_data = (void*)data; +} + +void ewk_tiled_backing_store_updates_process_post_set(Evas_Object *o, void *(*cb)(void *data, void *pre_data, Evas_Object *o), const void *data) +{ + PRIV_DATA_GET_OR_RETURN(o, priv); + priv->process.post_cb = cb; + priv->process.post_data = (void*)data; +} + +void ewk_tiled_backing_store_updates_process(Evas_Object *o) +{ + PRIV_DATA_GET_OR_RETURN(o, priv); + _ewk_tiled_backing_store_updates_process(priv); +} + +void ewk_tiled_backing_store_updates_clear(Evas_Object *o) +{ + PRIV_DATA_GET_OR_RETURN(o, priv); + + ewk_tile_matrix_updates_clear(priv->model.matrix); +} + +void ewk_tiled_backing_store_contents_resize(Evas_Object *o, Evas_Coord width, Evas_Coord height) +{ + PRIV_DATA_GET_OR_RETURN(o, priv); + + if (width == priv->model.width && height == priv->model.height) + return; + + priv->model.width = width; + priv->model.height = height; + priv->changed.model = EINA_TRUE; + + DBG("width,height=%d, %d", width, height); + _ewk_tiled_backing_store_changed(priv); +} + +void ewk_tiled_backing_store_disabled_update_set(Evas_Object *o, Eina_Bool value) +{ + PRIV_DATA_GET_OR_RETURN(o, priv); + + if (value != priv->render.disabled) + priv->render.disabled = value; +} + +void ewk_tiled_backing_store_flush(Evas_Object *o) +{ + PRIV_DATA_GET_OR_RETURN(o, priv); + Ewk_Tile_Unused_Cache *tuc = NULL; + + priv->view.offset.cur.x = 0; + priv->view.offset.cur.y = 0; + priv->view.offset.old.x = 0; + priv->view.offset.old.y = 0; + priv->view.offset.base.x = -priv->view.tile.w; + priv->view.offset.base.y = -priv->view.tile.h; + priv->model.base.col = -1; + priv->model.base.row = -1; + priv->changed.size = EINA_TRUE; + +#ifdef DEBUG_MEM_LEAKS + printf("\nFLUSHED BACKING STORE, STATUS BEFORE DELETING TILE MATRIX:\n"); + _ewk_tiled_backing_store_mem_dbg(priv); +#endif + + _ewk_tiled_backing_store_pre_render_request_flush(priv); + _ewk_tiled_backing_store_tile_dissociate_all(priv); + tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix); + ewk_tile_unused_cache_clear(tuc); + +#ifdef DEBUG_MEM_LEAKS + printf("\nFLUSHED BACKING STORE, STATUS AFTER RECREATING TILE MATRIX:\n"); + _ewk_tiled_backing_store_mem_dbg(priv); +#endif +} + +Eina_Bool ewk_tiled_backing_store_pre_render_region(Evas_Object *o, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h, float zoom) +{ + PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE); + Eina_Tile_Grid_Slicer slicer; + const Eina_Tile_Grid_Info *info; + Evas_Coord tw, th; + Ewk_Tile_Unused_Cache *tuc; + + tw = TILE_SIZE_AT_ZOOM(TILE_W, zoom); + tw = (tw >> 1) << 1; + zoom = TILE_W_ZOOM_AT_SIZE(tw); + /* WARNING: assume reverse zoom is the same for both axis */ + th = TILE_SIZE_AT_ZOOM(TILE_H, zoom); + + if (!eina_tile_grid_slicer_setup(&slicer, x, y, w, h, tw, th)) { + ERR("could not setup grid slicer for %d,%d+%dx%d tile=%dx%d", + x, y, w, h, tw, th); + return EINA_FALSE; + } + + while (eina_tile_grid_slicer_next(&slicer, &info)) { + const unsigned long c = info->col; + const unsigned long r = info->row; + if (!_ewk_tiled_backing_store_pre_render_request_add(priv, c, r, zoom, NULL, PRE_RENDER_PRIORITY_LOW)) + break; + } + + _ewk_tiled_backing_store_item_process_idler_start(priv); + + tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix); + ewk_tile_unused_cache_lock_area(tuc, x, y, w, h, zoom); + return EINA_TRUE; +} + +Eina_Bool ewk_tiled_backing_store_pre_render_relative_radius(Evas_Object *o, unsigned int n, float zoom) +{ + PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE); + unsigned long start_row, end_row, start_col, end_col, i, j, w, h; + Ewk_Tile_Unused_Cache *tuc; + + INF("priv->model.base.row =%ld, n=%u priv->view.rows=%lu", + priv->model.base.row, n, priv->view.rows); + start_row = (long)priv->model.base.row - n; + start_col = (long)priv->model.base.col - n; + end_row = MIN(priv->model.cur.rows - 1, + priv->model.base.row + priv->view.rows + n - 1); + end_col = MIN(priv->model.cur.cols - 1, + priv->model.base.col + priv->view.cols + n - 1); + + INF("start_row=%lu, end_row=%lu, start_col=%lu, end_col=%lu", + start_row, end_row, start_col, end_col); + + for (i = start_row; i <= end_row; i++) + for (j = start_col; j <= end_col; j++) + if (!_ewk_tiled_backing_store_pre_render_request_add(priv, j, i, zoom, NULL, PRE_RENDER_PRIORITY_LOW)) + goto start_processing; + +start_processing: + _ewk_tiled_backing_store_item_process_idler_start(priv); + + tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix); + h = (end_row - start_row + 1) * TILE_SIZE_AT_ZOOM(TILE_H, zoom); + w = (end_col - start_col + 1) * TILE_SIZE_AT_ZOOM(TILE_W, zoom); + ewk_tile_unused_cache_lock_area(tuc, + start_col * TILE_SIZE_AT_ZOOM(TILE_W, zoom), + start_row * TILE_SIZE_AT_ZOOM(TILE_H, zoom), w, h, zoom); + + return EINA_TRUE; +} + +void ewk_tiled_backing_store_pre_render_cancel(Evas_Object *o) +{ + PRIV_DATA_GET_OR_RETURN(o, priv); + Ewk_Tile_Unused_Cache *tuc; + + _ewk_tiled_backing_store_pre_render_request_remove_unassociated(priv); + + tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix); + ewk_tile_unused_cache_unlock_area(tuc); +} + +Eina_Bool ewk_tiled_backing_store_disable_render(Evas_Object *o) +{ + PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE); + return _ewk_tiled_backing_store_disable_render(priv); +} + +Eina_Bool ewk_tiled_backing_store_enable_render(Evas_Object *o) +{ + PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE); + _ewk_tiled_backing_store_changed(priv); + return _ewk_tiled_backing_store_enable_render(priv); +} + +/** + * Set the process_entire_queue flag of the renderer idler. + * + * + * @param o the tiled backing store object + * @param value EINA_TRUE if we want to process all the request of our queue. EINA_FALSE otherwise. + */ +void ewk_tiled_backing_store_process_entire_queue_set(Evas_Object *o, Eina_Bool value) +{ + PRIV_DATA_GET_OR_RETURN(o, priv); + if (priv->render.process_entire_queue != value) + priv->render.process_entire_queue = value; +} diff --git a/WebKit/efl/ewk/ewk_tiled_backing_store.h b/WebKit/efl/ewk/ewk_tiled_backing_store.h new file mode 100644 index 0000000..c8cdbb8 --- /dev/null +++ b/WebKit/efl/ewk/ewk_tiled_backing_store.h @@ -0,0 +1,130 @@ +/* + 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. +*/ + +#ifndef ewk_tiled_backing_store_h +#define ewk_tiled_backing_store_h + +#include "EWebKit.h" + +/* Enable accounting of render time in tile statistics */ +// #define TILE_STATS_ACCOUNT_RENDER_TIME + + +/* If define ewk will do more accounting to check for memory leaks + * try "kill -USR1 $PID" to get instantaneous debug + * try "kill -USR2 $PID" to get instantaneous debug and force flush of cache + */ +#define DEBUG_MEM_LEAKS 1 + +#define TILE_W (256) +#define TILE_H (256) + +#define ZOOM_STEP_MIN (0.01) + +#define TILE_SIZE_AT_ZOOM(SIZE, ZOOM) ((int)roundf((SIZE) * (ZOOM))) +#define TILE_W_ZOOM_AT_SIZE(SIZE) ((float)SIZE / (float)TILE_W) +#define TILE_H_ZOOM_AT_SIZE(SIZE) ((float)SIZE / (float)TILE_H) + +#ifdef __cplusplus +extern "C" { +#endif + +#include <Evas.h> +#include <cairo.h> + +typedef struct _Ewk_Tile Ewk_Tile; +typedef struct _Ewk_Tile_Stats Ewk_Tile_Stats; +typedef struct _Ewk_Tile_Matrix Ewk_Tile_Matrix; + +struct _Ewk_Tile_Stats { + double last_used; /**< time of last use */ +#ifdef TILE_STATS_ACCOUNT_RENDER_TIME + double render_time; /**< amount of time this tile took to render */ +#endif + unsigned int area; /**< cache for (w * h) */ + unsigned int misses; /**< number of times it became dirty but not + * repainted at all since it was not visible. + */ + Eina_Bool full_update:1; /**< tile requires full size update */ +}; + +struct _Ewk_Tile { + EINA_INLIST; /**< sibling tiles at same (i, j) but other zoom */ + Eina_Tiler *updates; /**< updated/dirty areas */ + Ewk_Tile_Stats stats; /**< tile usage statistics */ + unsigned long col, row; /**< tile tile position */ + Evas_Coord x, y; /**< tile coordinate position */ + + /* TODO: does it worth to keep those or create on demand? */ + cairo_surface_t *surface; + cairo_t *cairo; + + /** Never ever change those after tile is created (respect const!) */ + const Evas_Coord w, h; /**< tile size (see TILE_SIZE_AT_ZOOM()) */ + const Evas_Colorspace cspace; /**< colorspace */ + const float zoom; /**< zoom level contents were rendered at */ + const size_t bytes; /**< bytes used in pixels. keep + * before pixels to guarantee + * alignement! + */ + int visible; /**< visibility counter of this tile */ + Evas_Object *image; /**< Evas Image, the tile to be rendered */ + uint8_t *pixels; +}; + +#include "ewk_tiled_matrix.h" +#include "ewk_tiled_model.h" + +/* view */ +EAPI Evas_Object *ewk_tiled_backing_store_add(Evas *e); + +EAPI void ewk_tiled_backing_store_render_cb_set(Evas_Object *o, Eina_Bool (*cb)(void *data, Ewk_Tile *t, const Eina_Rectangle *area), const void *data); + +EAPI Eina_Bool ewk_tiled_backing_store_scroll_full_offset_set(Evas_Object *o, Evas_Coord x, Evas_Coord y); +EAPI Eina_Bool ewk_tiled_backing_store_scroll_full_offset_add(Evas_Object *o, Evas_Coord dx, Evas_Coord dy); +EAPI Eina_Bool ewk_tiled_backing_store_scroll_inner_offset_add(Evas_Object *o, Evas_Coord dx, Evas_Coord dy, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h); + +EAPI Eina_Bool ewk_tiled_backing_store_zoom_set(Evas_Object *o, float *zoom, Evas_Coord cx, Evas_Coord cy, Evas_Coord *offx, Evas_Coord *offy); +EAPI Eina_Bool ewk_tiled_backing_store_zoom_weak_set(Evas_Object *o, float zoom, Evas_Coord cx, Evas_Coord cy); +EAPI void ewk_tiled_backing_store_fix_offsets(Evas_Object *o, Evas_Coord w, Evas_Coord h); +EAPI Eina_Bool ewk_tiled_backing_store_update(Evas_Object *o, const Eina_Rectangle *update); +EAPI void ewk_tiled_backing_store_updates_process_pre_set(Evas_Object *o, void *(*cb)(void *data, Evas_Object *o), const void *data); +EAPI void ewk_tiled_backing_store_updates_process_post_set(Evas_Object *o, void *(*cb)(void *data, void *pre_data, Evas_Object *o), const void *data); +EAPI void ewk_tiled_backing_store_process_entire_queue_set(Evas_Object *o, Eina_Bool value); +EAPI void ewk_tiled_backing_store_updates_process(Evas_Object *o); +EAPI void ewk_tiled_backing_store_updates_clear(Evas_Object *o); +EAPI void ewk_tiled_backing_store_contents_resize(Evas_Object *o, Evas_Coord width, Evas_Coord height); +EAPI void ewk_tiled_backing_store_disabled_update_set(Evas_Object *o, Eina_Bool value); +EAPI void ewk_tiled_backing_store_flush(Evas_Object *o); +EAPI void ewk_tiled_backing_store_enable_scale_set(Evas_Object *o, Eina_Bool value); + +EAPI Ewk_Tile_Unused_Cache *ewk_tiled_backing_store_tile_unused_cache_get(const Evas_Object *o); +EAPI void ewk_tiled_backing_store_tile_unused_cache_set(Evas_Object *o, Ewk_Tile_Unused_Cache *tuc); + +EAPI Eina_Bool ewk_tiled_backing_store_pre_render_region(Evas_Object *o, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h, float zoom); +EAPI Eina_Bool ewk_tiled_backing_store_pre_render_relative_radius(Evas_Object *o, unsigned int n, float zoom); +EAPI void ewk_tiled_backing_store_pre_render_cancel(Evas_Object *o); + +EAPI Eina_Bool ewk_tiled_backing_store_disable_render(Evas_Object *o); +EAPI Eina_Bool ewk_tiled_backing_store_enable_render(Evas_Object *o); +#ifdef __cplusplus +} +#endif +#endif // ewk_tiled_backing_store_h diff --git a/WebKit/efl/ewk/ewk_tiled_matrix.c b/WebKit/efl/ewk/ewk_tiled_matrix.c new file mode 100644 index 0000000..fccbfa1 --- /dev/null +++ b/WebKit/efl/ewk/ewk_tiled_matrix.c @@ -0,0 +1,771 @@ +/* + 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_matrix.h" + +#define _GNU_SOURCE +#include "ewk_tiled_backing_store.h" +#include "ewk_tiled_private.h" +#include <Eina.h> +#include <errno.h> +#include <inttypes.h> +#include <math.h> +#include <stdio.h> // XXX remove me later +#include <stdlib.h> +#include <string.h> + +static const Evas_Coord TILE_MATRIX_BASE_TILE_SIZE = 256; + +struct _Ewk_Tile_Matrix { + Eina_Matrixsparse *matrix; + Ewk_Tile_Unused_Cache *tuc; + Evas_Colorspace cspace; + struct { + void (*cb)(void *data, Ewk_Tile *t, const Eina_Rectangle *update); + void *data; + } render; + unsigned int frozen; + Eina_List *updates; +#ifdef DEBUG_MEM_LEAKS + struct { + struct { + uint64_t allocated, freed; + } tiles, bytes; + } stats; +#endif +}; + +#ifdef DEBUG_MEM_LEAKS +static uint64_t tiles_leaked = 0; +static uint64_t bytes_leaked = 0; +#endif + +/* called when matrixsparse is resized or freed */ +static void _ewk_tile_matrix_cell_free(void *user_data, void *cell_data) +{ + Ewk_Tile_Matrix *tm = user_data; + Eina_Inlist *l = cell_data; + + if (!l) + return; + + ewk_tile_unused_cache_freeze(tm->tuc); + + while (l) { + Ewk_Tile *t = (Ewk_Tile *)l; + l = l->next; + + if (t->updates || t->stats.full_update) + tm->updates = eina_list_remove(tm->updates, t); + + if (t->visible) + ERR("freeing cell that is visible, leaking tile %p", t); + else { + if (!ewk_tile_unused_cache_tile_get(tm->tuc, t)) + ERR("tile %p was not in cache %p? leaking...", t, tm->tuc); + else { + DBG("tile cell does not exist anymore, free it %p", t); + +#ifdef DEBUG_MEM_LEAKS + tm->stats.bytes.freed += t->bytes; + tm->stats.tiles.freed++; +#endif + + ewk_tile_free(t); + } + } + } + + ewk_tile_unused_cache_thaw(tm->tuc); +} + +/* called when cache of unused tile is flushed */ +static void _ewk_tile_matrix_tile_free(void *data, Ewk_Tile *t) +{ + Ewk_Tile_Matrix *tm = data; + Eina_Matrixsparse_Cell *cell; + Eina_Inlist *l, *old; + + if (!eina_matrixsparse_cell_idx_get(tm->matrix, t->row, t->col, &cell)) { + ERR("removing tile %p that was not in the matrix? Leaking...", t); + return; + } + + if (t->updates || t->stats.full_update) + tm->updates = eina_list_remove(tm->updates, t); + + old = eina_matrixsparse_cell_data_get(cell); + l = eina_inlist_remove(old, EINA_INLIST_GET(t)); + if (!l) { + /* set to null to avoid double free */ + eina_matrixsparse_cell_data_replace(cell, NULL, NULL); + eina_matrixsparse_cell_clear(cell); + } else if (old != l) + eina_matrixsparse_cell_data_replace(cell, l, NULL); + + if (EINA_UNLIKELY(!!t->visible)) { + ERR("cache of unused tiles requesting deletion of used tile %p? " + "Leaking...", t); + return; + } + +#ifdef DEBUG_MEM_LEAKS + tm->stats.bytes.freed += t->bytes; + tm->stats.tiles.freed++; +#endif + + ewk_tile_free(t); +} + +/** + * Creates a new matrix of tiles. + * + * The tile matrix is responsible for keeping tiles around and + * providing fast access to them. One can use it to retrieve new or + * existing tiles and give them back, allowing them to be + * freed/replaced by the cache. + * + * @param tuc cache of unused tiles or @c NULL to create one + * automatically. + * @param cols number of columns in the matrix. + * @param rows number of rows in the matrix. + * @param cspace the color space used to create tiles in this matrix. + * @param render_cb function used to render given tile update. + * @param data context to give back to @a render_cb. + * + * @return newly allocated instance on success, @c NULL on failure. + */ +Ewk_Tile_Matrix *ewk_tile_matrix_new(Ewk_Tile_Unused_Cache *tuc, unsigned long cols, unsigned long rows, Evas_Colorspace cspace, void (*render_cb)(void *data, Ewk_Tile *t, const Eina_Rectangle *update), const void *data) +{ + Ewk_Tile_Matrix *tm; + + CALLOC_OR_OOM_RET(tm, sizeof(Ewk_Tile_Matrix), NULL); + + tm->matrix = eina_matrixsparse_new(rows, cols, _ewk_tile_matrix_cell_free, tm); + if (!tm->matrix) { + ERR("could not create sparse matrix."); + free(tm); + return NULL; + } + + if (tuc) + tm->tuc = ewk_tile_unused_cache_ref(tuc); + else { + tm->tuc = ewk_tile_unused_cache_new(40960000); + if (!tm->tuc) { + ERR("no cache of unused tile!"); + eina_matrixsparse_free(tm->matrix); + free(tm); + return NULL; + } + } + + tm->cspace = cspace; + tm->render.cb = render_cb; + tm->render.data = (void *)data; + + return tm; +} + +/** + * Destroys tiles matrix, releasing its resources. + * + * The cache instance is unreferenced, possibly freeing it. + */ +void ewk_tile_matrix_free(Ewk_Tile_Matrix *tm) +{ +#ifdef DEBUG_MEM_LEAKS + uint64_t tiles, bytes; +#endif + + EINA_SAFETY_ON_NULL_RETURN(tm); + ewk_tile_unused_cache_freeze(tm->tuc); + + eina_matrixsparse_free(tm->matrix); + + ewk_tile_unused_cache_thaw(tm->tuc); + ewk_tile_unused_cache_unref(tm->tuc); + +#ifdef DEBUG_MEM_LEAKS + tiles = tm->stats.tiles.allocated - tm->stats.tiles.freed; + bytes = tm->stats.bytes.allocated - tm->stats.bytes.freed; + + tiles_leaked += tiles; + bytes_leaked += bytes; + + if (tiles || bytes) + ERR("tiled matrix leaked: tiles[+%"PRIu64",-%"PRIu64":%"PRIu64"] " + "bytes[+%"PRIu64",-%"PRIu64":%"PRIu64"]", + tm->stats.tiles.allocated, tm->stats.tiles.freed, tiles, + tm->stats.bytes.allocated, tm->stats.bytes.freed, bytes); + else if (tiles_leaked || bytes_leaked) + WRN("tiled matrix had no leaks: tiles[+%"PRIu64",-%"PRIu64"] " + "bytes[+%"PRIu64",-%"PRIu64"], but some other leaked " + "%"PRIu64" tiles (%"PRIu64" bytes)", + tm->stats.tiles.allocated, tm->stats.tiles.freed, + tm->stats.bytes.allocated, tm->stats.bytes.freed, + tiles_leaked, bytes_leaked); + else + INF("tiled matrix had no leaks: tiles[+%"PRIu64",-%"PRIu64"] " + "bytes[+%"PRIu64",-%"PRIu64"]", + tm->stats.tiles.allocated, tm->stats.tiles.freed, + tm->stats.bytes.allocated, tm->stats.bytes.freed); +#endif + + free(tm); +} + +/** + * Resize matrix to given number of rows and columns. + */ +void ewk_tile_matrix_resize(Ewk_Tile_Matrix *tm, unsigned long cols, unsigned long rows) +{ + EINA_SAFETY_ON_NULL_RETURN(tm); + eina_matrixsparse_size_set(tm->matrix, rows, cols); +} + +/** + * Get the cache of unused tiles in use by given matrix. + * + * No reference is taken to the cache. + */ +Ewk_Tile_Unused_Cache *ewk_tile_matrix_unused_cache_get(const Ewk_Tile_Matrix *tm) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(tm, NULL); + return tm->tuc; +} + +/** + * Get the exact tile for the given position and zoom. + * + * If the tile was unused then it's removed from the cache. + * + * After usage, please give it back using + * ewk_tile_matrix_tile_put(). If you just want to check if it exists, + * then use ewk_tile_matrix_tile_exact_exists(). + * + * @param tm the tile matrix to get tile from. + * @param col the column number. + * @param row the row number. + * @param zoom the exact zoom to use. + * + * @return The tile instance or @c NULL if none is found. If the tile + * was in the unused cache it will be @b removed (thus + * considered used) and one should give it back with + * ewk_tile_matrix_tile_put() afterwards. + * + * @see ewk_tile_matrix_tile_nearest_get() + * @see ewk_tile_matrix_tile_exact_get() + */ +Ewk_Tile *ewk_tile_matrix_tile_exact_get(Ewk_Tile_Matrix *tm, unsigned long col, unsigned int row, float zoom) +{ + Ewk_Tile *t, *item, *item_found = NULL; + Eina_Inlist *inl; + + t = eina_matrixsparse_data_idx_get(tm->matrix, row, col); + if (!t) + return NULL; + + if (t->zoom == zoom) + goto end; + + EINA_INLIST_FOREACH(EINA_INLIST_GET(t), item) { + if (item->zoom != zoom) + continue; + item_found = item; + break; + } + + if (!item_found) + return NULL; + + inl = eina_inlist_promote(EINA_INLIST_GET(t), EINA_INLIST_GET(item_found)); + eina_matrixsparse_data_idx_replace(tm->matrix, row, col, inl, NULL); + + end: + if (!t->visible) { + if (!ewk_tile_unused_cache_tile_get(tm->tuc, t)) + WRN("Ewk_Tile was unused but not in cache? bug!"); + } + + return t; +} + +/** + * Checks if tile of given zoom exists in matrix. + * + * @param tm the tile matrix to check tile existence. + * @param col the column number. + * @param row the row number. + * @param zoom the exact zoom to query. + * + * @return @c EINA_TRUE if found, @c EINA_FALSE otherwise. + * + * @see ewk_tile_matrix_tile_exact_get() + */ +Eina_Bool ewk_tile_matrix_tile_exact_exists(Ewk_Tile_Matrix *tm, unsigned long col, unsigned int row, float zoom) +{ + Ewk_Tile *t, *item; + + t = eina_matrixsparse_data_idx_get(tm->matrix, row, col); + if (!t) + return EINA_FALSE; + + EINA_INLIST_FOREACH(EINA_INLIST_GET(t), item) { + if (item->zoom == zoom) + return EINA_TRUE; + } + + return EINA_FALSE; +} + +/** + * Get the nearest tile for given position and zoom. + * + * The nearest tile is the one at the given position but with the + * closest zoom, this being the division of the tile zoom by the + * desired zoom, the closest to 1.0 gets it. + * + * If the tile was unused then it's removed from the cache. + * + * After usage, please give it back using ewk_tile_matrix_tile_put(). + * + * @param tm the tile matrix to get tile from. + * @param col the column number. + * @param row the row number. + * @param zoom the exact zoom to use. + * + * @return The tile instance or @c NULL if none is found. If the tile + * was in the unused cache it will be @b removed (thus + * considered used) and one should give it back with + * ewk_tile_matrix_tile_put() afterwards. + */ +Ewk_Tile *ewk_tile_matrix_tile_nearest_get(Ewk_Tile_Matrix *tm, unsigned long col, unsigned int row, float zoom) +{ + Ewk_Tile *t, *item, *item_found = NULL; + Eina_Inlist *inl; + float zoom_found = 0; + + EINA_SAFETY_ON_NULL_RETURN_VAL(tm, NULL); + EINA_SAFETY_ON_FALSE_RETURN_VAL(zoom > 0.0, NULL); + + t = eina_matrixsparse_data_idx_get(tm->matrix, row, col); + if (!t) + return NULL; + + if (t->zoom == zoom) { + item_found = t; + goto end; + } + + EINA_INLIST_FOREACH(EINA_INLIST_GET(t), item) { + float cur_zoom = item->zoom; + + if (cur_zoom == zoom) { + item_found = item; + break; + } + + if (cur_zoom > zoom) + cur_zoom = zoom / cur_zoom; + else + cur_zoom = cur_zoom / zoom; + + if (cur_zoom > zoom_found) { + item_found = item; + zoom_found = cur_zoom; + } + } + + if (!item_found) + return NULL; + + inl = eina_inlist_promote(EINA_INLIST_GET(t), EINA_INLIST_GET(item_found)); + eina_matrixsparse_data_idx_replace(tm->matrix, row, col, inl, NULL); + + end: + if (!item_found->visible) { + if (!ewk_tile_unused_cache_tile_get(tm->tuc, item_found)) + WRN("Ewk_Tile was unused but not in cache? bug!"); + } + + return item_found; +} + +/** + * Create a new tile at given position and zoom level in the matrix. + * + * The newly created tile is considered in use and not put into cache + * of unused tiles. After it is used one should call + * ewk_tile_matrix_tile_put() to give it back to matrix. + * + * @param tm the tile matrix to create tile on. + * @param col the column number. + * @param row the row number. + * @param zoom the level to create tile, used to determine tile size. + */ +Ewk_Tile *ewk_tile_matrix_tile_new(Ewk_Tile_Matrix *tm, Evas *evas, unsigned long col, unsigned int row, float zoom) +{ + Evas_Coord s; + Eina_Inlist *old; + Ewk_Tile *t; + + EINA_SAFETY_ON_NULL_RETURN_VAL(tm, NULL); + EINA_SAFETY_ON_FALSE_RETURN_VAL(zoom > 0.0, NULL); + + s = TILE_SIZE_AT_ZOOM(TILE_MATRIX_BASE_TILE_SIZE, zoom); + zoom = (float)s / (float)TILE_MATRIX_BASE_TILE_SIZE; + + t = ewk_tile_new(evas, s, s, zoom, tm->cspace); + if (!t) { + ERR("could not create tile %dx%d at %f, cspace=%d", + s, s, (double)zoom, tm->cspace); + return NULL; + } + + old = eina_matrixsparse_data_idx_get(tm->matrix, row, col); + old = eina_inlist_prepend(old, EINA_INLIST_GET(t)); + if (!eina_matrixsparse_data_idx_replace(tm->matrix, row, col, t, NULL)) { + ERR("could not set matrix cell, row/col outside matrix dimensions!"); + ewk_tile_free(t); + return NULL; + } + + t->col = col; + t->row = row; + t->x = col * s; + t->y = row * s; + + cairo_translate(t->cairo, -t->x, -t->y); + + t->stats.full_update = EINA_TRUE; + tm->updates = eina_list_append(tm->updates, t); + +#ifdef DEBUG_MEM_LEAKS + tm->stats.bytes.allocated += t->bytes; + tm->stats.tiles.allocated++; +#endif + + return t; +} + +/** + * Gives back the tile to the tile matrix. + * + * This will report the tile is no longer in use by the one that got + * it with ewk_tile_matrix_tile_nearest_get() or + * ewk_tile_matrix_tile_exact_get(). + * + * Any previous reference to tile should be released + * (ewk_tile_hide()) before calling this function, so it will + * be known if it is not visibile anymore and thus can be put into the + * unused cache. + * + * @param tm the tile matrix to return tile to. + * @param t the tile instance to return, must @b not be @c NULL. + * @param last_used time in which tile was last used. + * + * @return #EINA_TRUE on success or #EINA_FALSE on failure. + */ +Eina_Bool ewk_tile_matrix_tile_put(Ewk_Tile_Matrix *tm, Ewk_Tile *t, double last_used) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(tm, EINA_FALSE); + EINA_SAFETY_ON_NULL_RETURN_VAL(t, EINA_FALSE); + + if (t->visible) + return EINA_TRUE; + + t->stats.last_used = last_used; + return ewk_tile_unused_cache_tile_put(tm->tuc, t, _ewk_tile_matrix_tile_free, tm); +} + +Eina_Bool ewk_tile_matrix_tile_update(Ewk_Tile_Matrix *tm, unsigned long col, unsigned int row, const Eina_Rectangle *update) +{ + Ewk_Tile *l, *t; + Eina_Rectangle new_update; + EINA_SAFETY_ON_NULL_RETURN_VAL(tm, EINA_FALSE); + EINA_SAFETY_ON_NULL_RETURN_VAL(update, EINA_FALSE); + + memcpy(&new_update, update, sizeof(new_update)); + // check update is valid, otherwise return EINA_FALSE + if (update->x < 0 || update->y < 0 || update->w <= 0 || update->h <= 0) { + ERR("invalid update region."); + return EINA_FALSE; + } + + if (update->x + update->w - 1 >= TILE_MATRIX_BASE_TILE_SIZE) + new_update.w = TILE_MATRIX_BASE_TILE_SIZE - update->x; + if (update->y + update->h - 1 >= TILE_MATRIX_BASE_TILE_SIZE) + new_update.h = TILE_MATRIX_BASE_TILE_SIZE - update->y; + + l = eina_matrixsparse_data_idx_get(tm->matrix, row, col); + + if (!l) + return EINA_TRUE; + + EINA_INLIST_FOREACH(EINA_INLIST_GET(l), t) { + if (!t->updates && !t->stats.full_update) + tm->updates = eina_list_append(tm->updates, t); + new_update.x = roundf(t->zoom * new_update.x); + new_update.y = roundf(t->zoom * new_update.y); + new_update.w = roundf(t->zoom * new_update.w); + new_update.h = roundf(t->zoom * new_update.h); + ewk_tile_update_area(t, &new_update); + } + + return EINA_TRUE; +} + +Eina_Bool ewk_tile_matrix_tile_update_full(Ewk_Tile_Matrix *tm, unsigned long col, unsigned int row) +{ + Ewk_Tile *l, *t; + Eina_Matrixsparse_Cell *cell; + EINA_SAFETY_ON_NULL_RETURN_VAL(tm, EINA_FALSE); + + if (!eina_matrixsparse_cell_idx_get(tm->matrix, row, col, &cell)) + return EINA_FALSE; + + if (!cell) + return EINA_TRUE; + + l = eina_matrixsparse_cell_data_get(cell); + if (!l) { + CRITICAL("matrix cell with no tile!"); + return EINA_TRUE; + } + + EINA_INLIST_FOREACH(EINA_INLIST_GET(l), t) { + if (!t->updates && !t->stats.full_update) + tm->updates = eina_list_append(tm->updates, t); + ewk_tile_update_full(t); + } + + return EINA_TRUE; +} + +void ewk_tile_matrix_tile_updates_clear(Ewk_Tile_Matrix *tm, Ewk_Tile *t) +{ + EINA_SAFETY_ON_NULL_RETURN(tm); + if (!t->updates && !t->stats.full_update) + return; + ewk_tile_updates_clear(t); + tm->updates = eina_list_remove(tm->updates, t); +} + +static Eina_Bool _ewk_tile_matrix_slicer_setup(Ewk_Tile_Matrix *tm, const Eina_Rectangle *area, float zoom, Eina_Tile_Grid_Slicer *slicer) +{ + unsigned long rows, cols; + Evas_Coord x, y, w, h, tw, th; + + if (area->w <= 0 || area->h <= 0) { + WRN("invalid area region: %d,%d+%dx%d.", + area->x, area->y, area->w, area->h); + return EINA_FALSE; + } + + x = area->x; + y = area->y; + w = area->w; + h = area->h; + + tw = TILE_SIZE_AT_ZOOM(TILE_W, zoom); + th = TILE_SIZE_AT_ZOOM(TILE_H, zoom); + + // cropping area region to fit matrix + eina_matrixsparse_size_get(tm->matrix, &rows, &cols); + if (x < 0) { + w += x; + x = 0; + } + if (y < 0) { + h += y; + y = 0; + } + + if (y + h - 1 > rows * th) + h = rows * th - y; + if (x + w - 1 > cols * tw) + w = cols * tw - x; + + return eina_tile_grid_slicer_setup(slicer, x, y, w, h, tw, th); +} + + +Eina_Bool ewk_tile_matrix_update(Ewk_Tile_Matrix *tm, const Eina_Rectangle *update, float zoom) +{ + const Eina_Tile_Grid_Info *info; + Eina_Tile_Grid_Slicer slicer; + EINA_SAFETY_ON_NULL_RETURN_VAL(tm, EINA_FALSE); + EINA_SAFETY_ON_NULL_RETURN_VAL(update, EINA_FALSE); + + if (update->w < 1 || update->h < 1) { + DBG("Why we get updates with empty areas? %d,%d+%dx%d at zoom %f", + update->x, update->y, update->w, update->h, zoom); + return EINA_TRUE; + } + + if (!_ewk_tile_matrix_slicer_setup(tm, update, zoom, &slicer)) { + ERR("Could not setup slicer for update %d,%d+%dx%d at zoom %f", + update->x, update->y, update->w, update->h, zoom); + return EINA_FALSE; + } + + while (eina_tile_grid_slicer_next(&slicer, &info)) { + unsigned long col, row; + Ewk_Tile *l, *t; + col = info->col; + row = info->row; + + l = eina_matrixsparse_data_idx_get(tm->matrix, row, col); + if (!l) + continue; + + EINA_INLIST_FOREACH(EINA_INLIST_GET(l), t) { + if (!t->updates && !t->stats.full_update) + tm->updates = eina_list_append(tm->updates, t); + if (info->full) + ewk_tile_update_full(t); + else { + if (t->zoom != zoom) + ewk_tile_update_full(t); + else + ewk_tile_update_area(t, &info->rect); + } + } + } + + + return EINA_TRUE; +} + +void ewk_tile_matrix_updates_process(Ewk_Tile_Matrix *tm) +{ + Eina_List *l, *l_next; + Ewk_Tile *t; + EINA_SAFETY_ON_NULL_RETURN(tm); + + // process updates, unflag tiles + EINA_LIST_FOREACH_SAFE(tm->updates, l, l_next, t) { + ewk_tile_updates_process(t, tm->render.cb, tm->render.data); + if (t->visible) { + ewk_tile_updates_clear(t); + tm->updates = eina_list_remove_list(tm->updates, l); + } + } +} + +void ewk_tile_matrix_updates_clear(Ewk_Tile_Matrix *tm) +{ + Ewk_Tile *t; + EINA_SAFETY_ON_NULL_RETURN(tm); + + EINA_LIST_FREE(tm->updates, t) + ewk_tile_updates_clear(t); + tm->updates = NULL; +} + +// remove me later! +void ewk_tile_matrix_dbg(const Ewk_Tile_Matrix *tm) +{ + Eina_Iterator *it = eina_matrixsparse_iterator_complete_new(tm->matrix); + Eina_Matrixsparse_Cell *cell; + Eina_Bool last_empty = EINA_FALSE; + +#ifdef DEBUG_MEM_LEAKS + printf("Ewk_Tile Matrix: tiles[+%"PRIu64",-%"PRIu64":%"PRIu64"] " + "bytes[+%"PRIu64",-%"PRIu64":%"PRIu64"]\n", + tm->stats.tiles.allocated, tm->stats.tiles.freed, + tm->stats.tiles.allocated - tm->stats.tiles.freed, + tm->stats.bytes.allocated, tm->stats.bytes.freed, + tm->stats.bytes.allocated - tm->stats.bytes.freed); +#else + printf("Ewk_Tile Matrix:\n"); +#endif + + EINA_ITERATOR_FOREACH(it, cell) { + unsigned long row, col; + Eina_Inlist *l; + eina_matrixsparse_cell_position_get(cell, &row, &col); + l = eina_matrixsparse_cell_data_get(cell); + + if (!l) { + if (!last_empty) { + last_empty = EINA_TRUE; + printf("Empty:"); + } + printf(" [%lu,%lu]", col, row); + } else { + if (last_empty) { + last_empty = EINA_FALSE; + printf("\n"); + } + Ewk_Tile *t; + + printf("%3lu,%3lu %10p:", col, row, l); + EINA_INLIST_FOREACH(l, t) + printf(" [%3lu,%3lu + %dx%d @ %0.3f]%c", + t->col, t->row, t->w, t->h, t->zoom, + t->visible ? '*': ' '); + printf("\n"); + } + } + if (last_empty) + printf("\n"); + eina_iterator_free(it); + + ewk_tile_unused_cache_dbg(tm->tuc); +} + +/** + * Freeze matrix to not do maintenance tasks. + * + * Maintenance tasks optimize 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_matrix_thaw() + */ +void ewk_tile_matrix_freeze(Ewk_Tile_Matrix *tm) +{ + EINA_SAFETY_ON_NULL_RETURN(tm); + if (!tm->frozen) + ewk_tile_unused_cache_freeze(tm->tuc); + tm->frozen++; +} + +/** + * Unfreezes maintenance tasks. + * + * If this is the last counterpart of freeze, then maintenance tasks + * will run immediately. + */ +void ewk_tile_matrix_thaw(Ewk_Tile_Matrix *tm) +{ + EINA_SAFETY_ON_NULL_RETURN(tm); + if (!tm->frozen) { + ERR("thawing more than freezing!"); + return; + } + + tm->frozen--; + if (!tm->frozen) + ewk_tile_unused_cache_thaw(tm->tuc); +} diff --git a/WebKit/efl/ewk/ewk_tiled_matrix.h b/WebKit/efl/ewk/ewk_tiled_matrix.h new file mode 100644 index 0000000..07b8612 --- /dev/null +++ b/WebKit/efl/ewk/ewk_tiled_matrix.h @@ -0,0 +1,60 @@ +/* + 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. +*/ + +#ifndef ewk_tiled_matrix_h +#define ewk_tiled_matrix_h + +#include "ewk_eapi.h" +#include "ewk_tiled_backing_store.h" + +#include <Evas.h> + +/* matrix of tiles */ +EAPI Ewk_Tile_Matrix *ewk_tile_matrix_new(Ewk_Tile_Unused_Cache *tuc, unsigned long cols, unsigned long rows, Evas_Colorspace cspace, void (*render_cb)(void *data, Ewk_Tile *t, const Eina_Rectangle *update), const void *data); +EAPI void ewk_tile_matrix_free(Ewk_Tile_Matrix *tm); + +EAPI void ewk_tile_matrix_resize(Ewk_Tile_Matrix *tm, unsigned long cols, unsigned long rows); + +EAPI Ewk_Tile_Unused_Cache *ewk_tile_matrix_unused_cache_get(const Ewk_Tile_Matrix *tm); + +EAPI Ewk_Tile *ewk_tile_matrix_tile_exact_get(Ewk_Tile_Matrix *tm, unsigned long col, unsigned int row, float zoom); +EAPI Eina_Bool ewk_tile_matrix_tile_exact_exists(Ewk_Tile_Matrix *tm, unsigned long col, unsigned int row, float zoom); +EAPI Ewk_Tile *ewk_tile_matrix_tile_nearest_get(Ewk_Tile_Matrix *tm, unsigned long col, unsigned int row, float zoom); +EAPI Ewk_Tile *ewk_tile_matrix_tile_new(Ewk_Tile_Matrix *tm, Evas *evas, unsigned long col, unsigned int row, float zoom); +EAPI Eina_Bool ewk_tile_matrix_tile_put(Ewk_Tile_Matrix *tm, Ewk_Tile *t, double last_used); + +EAPI Eina_Bool ewk_tile_matrix_tile_update(Ewk_Tile_Matrix *tm, unsigned long col, unsigned int row, const Eina_Rectangle *update); +EAPI Eina_Bool ewk_tile_matrix_tile_update_full(Ewk_Tile_Matrix *tm, unsigned long col, unsigned int row); +EAPI void ewk_tile_matrix_tile_updates_clear(Ewk_Tile_Matrix *tm, Ewk_Tile *t); + +EAPI Eina_Bool ewk_tile_matrix_update(Ewk_Tile_Matrix *tm, const Eina_Rectangle *update, float zoom); +EAPI void ewk_tile_matrix_updates_process(Ewk_Tile_Matrix *tm); +EAPI void ewk_tile_matrix_updates_clear(Ewk_Tile_Matrix *tm); +EAPI void ewk_tile_matrix_freeze(Ewk_Tile_Matrix *tm); +EAPI void ewk_tile_matrix_thaw(Ewk_Tile_Matrix *tm); + +// remove me! + void ewk_tile_matrix_dbg(const Ewk_Tile_Matrix *tm); + void ewk_tile_unused_cache_dbg(const Ewk_Tile_Unused_Cache *tuc); + void ewk_tile_accounting_dbg(void); + + +#endif // ewk_tiled_matrix_h + diff --git a/WebKit/efl/ewk/ewk_tiled_model.c b/WebKit/efl/ewk/ewk_tiled_model.c new file mode 100644 index 0000000..641b93e --- /dev/null +++ b/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 = {0, 0, 0, 0}; + 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"); +} diff --git a/WebKit/efl/ewk/ewk_tiled_model.h b/WebKit/efl/ewk/ewk_tiled_model.h new file mode 100644 index 0000000..a546cee --- /dev/null +++ b/WebKit/efl/ewk/ewk_tiled_model.h @@ -0,0 +1,58 @@ +/* + 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. +*/ + +#ifndef ewk_tiled_model_h +#define ewk_tiled_model_h + +#include "ewk_eapi.h" +#include "ewk_tiled_backing_store.h" + +#include <Evas.h> + +/* model */ +EAPI Ewk_Tile *ewk_tile_new(Evas *evas, Evas_Coord w, Evas_Coord h, float zoom, Evas_Colorspace cspace); +EAPI void ewk_tile_free(Ewk_Tile *t); +EAPI void ewk_tile_unused_cache_clear(Ewk_Tile_Unused_Cache *tuc); +EAPI void ewk_tile_show(Ewk_Tile *t); +EAPI void ewk_tile_hide(Ewk_Tile *t); +Eina_Bool ewk_tile_visible_get(Ewk_Tile *t); +EAPI void ewk_tile_update_full(Ewk_Tile *t); +EAPI void ewk_tile_update_area(Ewk_Tile *t, const Eina_Rectangle *r); +EAPI void ewk_tile_updates_process(Ewk_Tile *t, void (*cb)(void *data, Ewk_Tile *t, const Eina_Rectangle *update), const void *data); +EAPI void ewk_tile_updates_clear(Ewk_Tile *t); + +/* cache of unused tiles */ +EAPI Ewk_Tile_Unused_Cache *ewk_tile_unused_cache_new(size_t max); +EAPI 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); +EAPI void ewk_tile_unused_cache_unlock_area(Ewk_Tile_Unused_Cache *tuc); +EAPI void ewk_tile_unused_cache_free(Ewk_Tile_Unused_Cache *tuc); +EAPI Ewk_Tile_Unused_Cache *ewk_tile_unused_cache_ref(Ewk_Tile_Unused_Cache *tuc); +EAPI void ewk_tile_unused_cache_unref(Ewk_Tile_Unused_Cache *tuc); + +EAPI void ewk_tile_unused_cache_dirty(Ewk_Tile_Unused_Cache *tuc); + +EAPI void ewk_tile_unused_cache_freeze(Ewk_Tile_Unused_Cache *tuc); +EAPI void ewk_tile_unused_cache_thaw(Ewk_Tile_Unused_Cache *tuc); + +EAPI Eina_Bool ewk_tile_unused_cache_tile_get(Ewk_Tile_Unused_Cache *tuc, Ewk_Tile *t); +EAPI 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); + +#endif // ewk_tiled_model_h + diff --git a/WebKit/efl/ewk/ewk_tiled_private.h b/WebKit/efl/ewk/ewk_tiled_private.h new file mode 100644 index 0000000..ce65f43 --- /dev/null +++ b/WebKit/efl/ewk/ewk_tiled_private.h @@ -0,0 +1,62 @@ +/* + 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. +*/ + +#ifndef ewk_tiled_private_h +#define ewk_tiled_private_h + +/* logging */ +extern int _ewk_tiled_log_dom; + +#define CRITICAL(...) EINA_LOG_DOM_CRIT(_ewk_tiled_log_dom, __VA_ARGS__) +#define ERR(...) EINA_LOG_DOM_ERR(_ewk_tiled_log_dom, __VA_ARGS__) +#define WRN(...) EINA_LOG_DOM_WARN(_ewk_tiled_log_dom, __VA_ARGS__) +#define INF(...) EINA_LOG_DOM_INFO(_ewk_tiled_log_dom, __VA_ARGS__) +#define DBG(...) EINA_LOG_DOM_DBG(_ewk_tiled_log_dom, __VA_ARGS__) +#define OOM(op, size) CRITICAL("could not %s %zd bytes: %s", op, size, strerror(errno)) +#define MALLOC_OR_OOM_RET(ptr, size, ...) \ + do { \ + ptr = malloc(size); \ + if (!ptr && (size) > 0) { \ + OOM("malloc", (size)); \ + return __VA_ARGS__; \ + } \ + } while (0) + +#define CALLOC_OR_OOM_RET(ptr, size, ...) \ + do { \ + ptr = calloc(1, size); \ + if (!ptr && (size) > 0) { \ + OOM("calloc", (size)); \ + return __VA_ARGS__; \ + } \ + } while (0) + +#define REALLOC_OR_OOM_RET(ptr, size, ...) \ + do { \ + void *__tmp_ptr; \ + __tmp_ptr = realloc(ptr, size); \ + if (!__tmp_ptr && (size) > 0) { \ + OOM("realloc", (size)); \ + return __VA_ARGS__; \ + } \ + ptr = __tmp_ptr; \ + } while (0) + +#endif // ewk_tiled_private_h diff --git a/WebKit/efl/ewk/ewk_view.cpp b/WebKit/efl/ewk/ewk_view.cpp index 5ff3ade..5147b6d 100644 --- a/WebKit/efl/ewk/ewk_view.cpp +++ b/WebKit/efl/ewk/ewk_view.cpp @@ -94,6 +94,9 @@ struct _Ewk_View_Private_Data { } scrolls; unsigned int imh; /**< input method hints */ struct { + Eina_Bool view_cleared:1; + } flags; + struct { const char* user_agent; const char* user_stylesheet; const char* encoding_default; @@ -725,6 +728,12 @@ static void _ewk_view_smart_add(Evas_Object* o) evas_object_smart_member_add(sd->backing_store, o); evas_object_show(sd->backing_store); + evas_object_pass_events_set(sd->backing_store, EINA_TRUE); + + sd->events_rect = evas_object_rectangle_add(sd->base.evas); + evas_object_color_set(sd->events_rect, 0, 0, 0, 0); + evas_object_smart_member_add(sd->events_rect, o); + evas_object_show(sd->events_rect); sd->main_frame = ewk_frame_add(sd->base.evas); if (!sd->main_frame) { @@ -815,6 +824,7 @@ static void _ewk_view_smart_calculate(Evas_Object* o) view->adjustViewSize(); } evas_object_resize(sd->main_frame, w, h); + evas_object_resize(sd->events_rect, w, h); sd->changed.frame_rect = EINA_TRUE; sd->view.w = w; sd->view.h = h; @@ -827,6 +837,7 @@ static void _ewk_view_smart_calculate(Evas_Object* o) if (sd->changed.position && ((x != sd->view.x) || (y != sd->view.y))) { evas_object_move(sd->main_frame, x, y); evas_object_move(sd->backing_store, x, y); + evas_object_move(sd->events_rect, x, y); sd->changed.frame_rect = EINA_TRUE; sd->view.x = x; sd->view.y = y; @@ -850,6 +861,25 @@ static void _ewk_view_smart_calculate(Evas_Object* o) } } +static void _ewk_view_smart_show(Evas_Object *o) +{ + EWK_VIEW_SD_GET(o, sd); + EWK_VIEW_PRIV_GET(sd, priv); + + if (evas_object_clipees_get(sd->base.clipper)) + evas_object_show(sd->base.clipper); + evas_object_show(sd->backing_store); +} + +static void _ewk_view_smart_hide(Evas_Object *o) +{ + EWK_VIEW_SD_GET(o, sd); + EWK_VIEW_PRIV_GET(sd, priv); + + evas_object_hide(sd->base.clipper); + evas_object_hide(sd->backing_store); +} + static Eina_Bool _ewk_view_smart_contents_resize(Ewk_View_Smart_Data* sd, int w, int h) { return EINA_TRUE; @@ -897,6 +927,13 @@ static Eina_Bool _ewk_view_smart_pre_render_region(Ewk_View_Smart_Data* sd, Evas return EINA_FALSE; } +static Eina_Bool _ewk_view_smart_pre_render_relative_radius(Ewk_View_Smart_Data* sd, unsigned int n, float zoom) +{ + WRN("not supported by engine. sd=%p, n=%u zoom=%f", + sd, n, zoom); + return EINA_FALSE; +} + static void _ewk_view_smart_pre_render_cancel(Ewk_View_Smart_Data* sd) { WRN("not supported by engine. sd=%p", sd); @@ -954,6 +991,7 @@ static Eina_Bool _ewk_view_zoom_animator_cb(void* data) || (now < priv->animated_zoom.time.start)) { _ewk_view_zoom_animated_finish(sd); ewk_view_zoom_set(sd->self, priv->animated_zoom.zoom.end, cx, cy); + sd->api->sc.calculate(sd->self); return EINA_FALSE; } @@ -991,6 +1029,18 @@ static WebCore::ViewportAttributes _ewk_view_viewport_attributes_compute(Evas_Ob return attributes; } +static Eina_Bool _ewk_view_smart_disable_render(Ewk_View_Smart_Data *sd) +{ + WRN("not supported by engine. sd=%p", sd); + return EINA_FALSE; +} + +static Eina_Bool _ewk_view_smart_enable_render(Ewk_View_Smart_Data *sd) +{ + WRN("not supported by engine. sd=%p", sd); + return EINA_FALSE; +} + /** * Sets the smart class api without any backing store, enabling view * to be inherited. @@ -1030,13 +1080,18 @@ Eina_Bool ewk_view_base_smart_set(Ewk_View_Smart_Class* api) api->sc.resize = _ewk_view_smart_resize; api->sc.move = _ewk_view_smart_move; api->sc.calculate = _ewk_view_smart_calculate; + api->sc.show = _ewk_view_smart_show; + api->sc.hide = _ewk_view_smart_hide; api->sc.data = EWK_VIEW_TYPE_STR; /* used by type checking */ api->contents_resize = _ewk_view_smart_contents_resize; api->zoom_set = _ewk_view_smart_zoom_set; api->flush = _ewk_view_smart_flush; api->pre_render_region = _ewk_view_smart_pre_render_region; + api->pre_render_relative_radius = _ewk_view_smart_pre_render_relative_radius; api->pre_render_cancel = _ewk_view_smart_pre_render_cancel; + api->disable_render = _ewk_view_smart_disable_render; + api->enable_render = _ewk_view_smart_enable_render; api->focus_in = _ewk_view_smart_focus_in; api->focus_out = _ewk_view_smart_focus_out; @@ -2171,10 +2226,19 @@ Eina_Bool ewk_view_zoom_text_only_set(Evas_Object* o, Eina_Bool setting) Eina_Bool ewk_view_pre_render_region(Evas_Object* o, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h, float zoom) { EWK_VIEW_SD_GET_OR_RETURN(o, sd, EINA_FALSE); + EWK_VIEW_PRIV_GET_OR_RETURN(sd, priv, EINA_FALSE); EINA_SAFETY_ON_NULL_RETURN_VAL(sd->api->pre_render_region, EINA_FALSE); - float cur_zoom = ewk_frame_zoom_get(sd->main_frame); + float cur_zoom; Evas_Coord cw, ch; + /* When doing animated zoom it's not possible to call pre-render since it + * would screw up parameters that animation is currently using + */ + if (priv->animated_zoom.animator) + return EINA_FALSE; + + cur_zoom = ewk_frame_zoom_get(sd->main_frame); + if (cur_zoom < 0.00001) return EINA_FALSE; if (!ewk_frame_contents_size_get(sd->main_frame, &cw, &ch)) @@ -2203,6 +2267,35 @@ Eina_Bool ewk_view_pre_render_region(Evas_Object* o, Evas_Coord x, Evas_Coord y, } /** + * Hint engine to pre-render region, given n extra cols/rows + * + * This is an alternative method to ewk_view_pre_render_region(). It does not + * make sense in all engines and therefore it might not be implemented at all. + * + * It's only useful if engine divide the area being rendered in smaller tiles, + * forming a grid. Then, browser could call this function to pre-render @param n + * rows/cols involving the current viewport. + * + * @param o view to ask pre-render on. + * @param n number of cols/rows that must be part of the region pre-rendered + * + * @see ewk_view_pre_render_region() + */ +Eina_Bool ewk_view_pre_render_relative_radius(Evas_Object* o, unsigned int n) +{ + EWK_VIEW_SD_GET_OR_RETURN(o, sd, EINA_FALSE); + EWK_VIEW_PRIV_GET_OR_RETURN(sd, priv, EINA_FALSE); + EINA_SAFETY_ON_NULL_RETURN_VAL(sd->api->pre_render_relative_radius, EINA_FALSE); + float cur_zoom; + + if (priv->animated_zoom.animator) + return EINA_FALSE; + + cur_zoom = ewk_frame_zoom_get(sd->main_frame); + return sd->api->pre_render_relative_radius(sd, n, cur_zoom); +} + +/** * Get input method hints * * @param o View. @@ -2228,6 +2321,36 @@ void ewk_view_pre_render_cancel(Evas_Object* o) sd->api->pre_render_cancel(sd); } +/** + * Enable processing of update requests. + * + * @param o view to enable rendering. + * + * @return @c EINA_TRUE if render was enabled, @c EINA_FALSE + otherwise (errors, rendering suspension not supported). + */ +Eina_Bool ewk_view_enable_render(const Evas_Object *o) +{ + EWK_VIEW_SD_GET_OR_RETURN(o, sd, EINA_FALSE); + EINA_SAFETY_ON_NULL_RETURN_VAL(sd->api->enable_render, EINA_FALSE); + return sd->api->enable_render(sd); +} + +/** + * Disable processing of update requests. + * + * @param o view to disable rendering. + * + * @return @c EINA_TRUE if render was disabled, @c EINA_FALSE + otherwise (errors, rendering suspension not supported). + */ +Eina_Bool ewk_view_disable_render(const Evas_Object *o) +{ + EWK_VIEW_SD_GET_OR_RETURN(o, sd, EINA_FALSE); + EINA_SAFETY_ON_NULL_RETURN_VAL(sd->api->disable_render, EINA_FALSE); + return sd->api->disable_render(sd); +} + const char* ewk_view_setting_user_agent_get(const Evas_Object* o) { EWK_VIEW_SD_GET_OR_RETURN(o, sd, 0); @@ -3952,7 +4075,6 @@ void ewk_view_scroll(Evas_Object* o, Evas_Coord dx, Evas_Coord dy, Evas_Coord sx EWK_VIEW_PRIV_GET_OR_RETURN(sd, priv); EINA_SAFETY_ON_TRUE_RETURN(!dx && !dy); - _ewk_view_scroll_add(priv, dx, dy, sx, sy, sw, sh, main_frame); _ewk_view_smart_changed(sd); } @@ -4284,6 +4406,42 @@ float ewk_view_device_pixel_ratio_get(Evas_Object* o) return priv->settings.device_pixel_ratio; } +void ewk_view_did_first_visually_nonempty_layout(Evas_Object *o) +{ + EWK_VIEW_SD_GET_OR_RETURN(o, sd); + EWK_VIEW_PRIV_GET_OR_RETURN(sd, priv); + if (!priv->flags.view_cleared) { + ewk_view_frame_main_cleared(o); + ewk_view_enable_render(o); + priv->flags.view_cleared = EINA_TRUE; + } +} + +/** + * @internal + * Dispatch finished loading. + * + * @param o view. + */ +void ewk_view_dispatch_did_finish_loading(Evas_Object *o) +{ + /* If we reach this point and rendering is still disabled, WebCore will not + * trigger the didFirstVisuallyNonEmptyLayout signal anymore. So, we + * forcefully re-enable the rendering. + */ + ewk_view_did_first_visually_nonempty_layout(o); +} + +void ewk_view_transition_to_commited_for_newpage(Evas_Object *o) +{ + EWK_VIEW_SD_GET_OR_RETURN(o, sd); + EWK_VIEW_PRIV_GET_OR_RETURN(sd, priv); + + ewk_view_disable_render(o); + priv->flags.view_cleared = EINA_FALSE; +} + + /** * @internal * Reports a requeset will be loaded. It's client responsibility to decide if diff --git a/WebKit/efl/ewk/ewk_view.h b/WebKit/efl/ewk/ewk_view.h index 1c4de34..6902949 100644 --- a/WebKit/efl/ewk/ewk_view.h +++ b/WebKit/efl/ewk/ewk_view.h @@ -114,7 +114,11 @@ struct _Ewk_View_Smart_Class { void (*bg_color_set)(Ewk_View_Smart_Data *sd, unsigned char r, unsigned char g, unsigned char b, unsigned char a); void (*flush)(Ewk_View_Smart_Data *sd); Eina_Bool (*pre_render_region)(Ewk_View_Smart_Data *sd, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h, float zoom); + Eina_Bool (*pre_render_relative_radius)(Ewk_View_Smart_Data *sd, unsigned int n, float zoom); void (*pre_render_cancel)(Ewk_View_Smart_Data *sd); + Eina_Bool (*disable_render)(Ewk_View_Smart_Data *sd); + Eina_Bool (*enable_render)(Ewk_View_Smart_Data *sd); + // event handling: // - returns true if handled // - if overridden, have to call parent method if desired @@ -151,7 +155,7 @@ struct _Ewk_View_Smart_Class { * @see EWK_VIEW_SMART_CLASS_INIT_VERSION * @see EWK_VIEW_SMART_CLASS_INIT_NAME_VERSION */ -#define EWK_VIEW_SMART_CLASS_INIT(smart_class_init) {smart_class_init, EWK_VIEW_SMART_CLASS_VERSION, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define EWK_VIEW_SMART_CLASS_INIT(smart_class_init) {smart_class_init, EWK_VIEW_SMART_CLASS_VERSION, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /** * Initializer to zero a whole Ewk_View_Smart_Class structure. @@ -268,6 +272,7 @@ struct _Ewk_View_Smart_Data { Evas_Object *self; /**< reference to owner object */ Evas_Object *main_frame; /**< reference to main frame object */ Evas_Object *backing_store; /**< reference to backing store */ + Evas_Object *events_rect; /**< rectangle that should receive mouse events */ Ewk_View_Private_Data *_priv; /**< should never be accessed, c++ stuff */ struct { Evas_Coord x, y, w, h; /**< last used viewport */ @@ -291,10 +296,36 @@ struct _Ewk_View_Smart_Data { } changed; }; +/** + * Cache (pool) that contains unused tiles for ewk_view_tiled. + * + * This cache will maintain unused tiles and flush them when the total + * memory exceeds the set amount when + * ewk_tile_unused_cache_auto_flush() or explicitly set value when + * ewk_tile_unused_cache_flush() is called. + * + * The tile may be shared among different ewk_view_tiled instances to + * group maximum unused memory resident in the system. + */ +typedef struct _Ewk_Tile_Unused_Cache Ewk_Tile_Unused_Cache; +EAPI void ewk_tile_unused_cache_max_set(Ewk_Tile_Unused_Cache *tuc, size_t max); +EAPI size_t ewk_tile_unused_cache_max_get(const Ewk_Tile_Unused_Cache *tuc); +EAPI size_t ewk_tile_unused_cache_used_get(const Ewk_Tile_Unused_Cache *tuc); +EAPI size_t ewk_tile_unused_cache_flush(Ewk_Tile_Unused_Cache *tuc, size_t bytes); +EAPI void ewk_tile_unused_cache_auto_flush(Ewk_Tile_Unused_Cache *tuc); + EAPI Eina_Bool ewk_view_base_smart_set(Ewk_View_Smart_Class *api); EAPI Eina_Bool ewk_view_single_smart_set(Ewk_View_Smart_Class *api); +EAPI Eina_Bool ewk_view_tiled_smart_set(Ewk_View_Smart_Class *api); EAPI Evas_Object *ewk_view_single_add(Evas *e); +EAPI Evas_Object *ewk_view_tiled_add(Evas *e); + +EAPI Ewk_Tile_Unused_Cache *ewk_view_tiled_unused_cache_get(const Evas_Object *o); +EAPI void ewk_view_tiled_unused_cache_set(Evas_Object *o, Ewk_Tile_Unused_Cache *cache); + +// FIXME: this function should be removed later, when we find the best flag to use. +EAPI void ewk_view_tiled_process_entire_queue_set(Evas_Object *o, Eina_Bool flag); EAPI void ewk_view_fixed_layout_size_set(Evas_Object *o, Evas_Coord w, Evas_Coord h); EAPI void ewk_view_fixed_layout_size_get(Evas_Object *o, Evas_Coord *w, Evas_Coord *h); @@ -370,7 +401,10 @@ EAPI Eina_Bool ewk_view_zoom_text_only_get(const Evas_Object *o); EAPI Eina_Bool ewk_view_zoom_text_only_set(Evas_Object *o, Eina_Bool setting); EAPI Eina_Bool ewk_view_pre_render_region(Evas_Object *o, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h, float zoom); +EAPI Eina_Bool ewk_view_pre_render_relative_radius(Evas_Object *o, unsigned int n); EAPI void ewk_view_pre_render_cancel(Evas_Object *o); +EAPI Eina_Bool ewk_view_enable_render(const Evas_Object *o); +EAPI Eina_Bool ewk_view_disable_render(const Evas_Object *o); EAPI unsigned int ewk_view_imh_get(Evas_Object *o); diff --git a/WebKit/efl/ewk/ewk_view_tiled.c b/WebKit/efl/ewk/ewk_view_tiled.c new file mode 100644 index 0000000..d380e8e --- /dev/null +++ b/WebKit/efl/ewk/ewk_view_tiled.c @@ -0,0 +1,346 @@ +/* + 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_view.h" + +#include "ewk_logging.h" + +#include <Evas.h> +#include <eina_safety_checks.h> +#include <ewk_tiled_backing_store.h> + +static Ewk_View_Smart_Class _parent_sc = EWK_VIEW_SMART_CLASS_INIT_NULL; + +static Eina_Bool _ewk_view_tiled_render_cb(void *data, Ewk_Tile *t, const Eina_Rectangle *area) +{ + Ewk_View_Private_Data *priv = (Ewk_View_Private_Data*)data; + Eina_Rectangle r = {area->x + t->x, area->y + t->y, area->w, area->h}; + + return ewk_view_paint_contents(priv, t->cairo, &r); +} + +static void *_ewk_view_tiled_updates_process_pre(void *data, Evas_Object *o) +{ + Ewk_View_Private_Data *priv = (Ewk_View_Private_Data*)data; + ewk_view_layout_if_needed_recursive(priv); + return 0; +} + +static Evas_Object *_ewk_view_tiled_smart_backing_store_add(Ewk_View_Smart_Data *sd) +{ + Evas_Object *bs = ewk_tiled_backing_store_add(sd->base.evas); + ewk_tiled_backing_store_render_cb_set + (bs, _ewk_view_tiled_render_cb, sd->_priv); + ewk_tiled_backing_store_updates_process_pre_set + (bs, _ewk_view_tiled_updates_process_pre, sd->_priv); + return bs; +} + +static void +_ewk_view_tiled_contents_size_changed_cb(void *data, Evas_Object *o, void *event_info) +{ + Evas_Coord *size = (Evas_Coord*)event_info; + Ewk_View_Smart_Data *sd = (Ewk_View_Smart_Data*)data; + + ewk_tiled_backing_store_contents_resize + (sd->backing_store, size[0], size[1]); +} + +static void _ewk_view_tiled_smart_add(Evas_Object *o) +{ + Ewk_View_Smart_Data *sd; + + _parent_sc.sc.add(o); + + sd = (Ewk_View_Smart_Data*)evas_object_smart_data_get(o); + evas_object_smart_callback_add( + sd->main_frame, "contents,size,changed", + _ewk_view_tiled_contents_size_changed_cb, sd); + ewk_frame_paint_full_set(sd->main_frame, EINA_TRUE); +} + +static Eina_Bool _ewk_view_tiled_smart_scrolls_process(Ewk_View_Smart_Data *sd) +{ + const Ewk_Scroll_Request *sr; + const Ewk_Scroll_Request *sr_end; + size_t count; + Evas_Coord vw, vh; + + ewk_frame_contents_size_get(sd->main_frame, &vw, &vh); + + sr = ewk_view_scroll_requests_get(sd->_priv, &count); + sr_end = sr + count; + for (; sr < sr_end; sr++) { + if (sr->main_scroll) + ewk_tiled_backing_store_scroll_full_offset_add + (sd->backing_store, sr->dx, sr->dy); + else { + Evas_Coord sx, sy, sw, sh; + + sx = sr->x; + sy = sr->y; + sw = sr->w; + sh = sr->h; + + if (abs(sr->dx) >= sw || abs(sr->dy) >= sh) { + /* doubt webkit would be so stupid... */ + DBG("full page scroll %+03d,%+03d. convert to repaint %d,%d + %dx%d", + sr->dx, sr->dy, sx, sy, sw, sh); + ewk_view_repaint_add(sd->_priv, sx, sy, sw, sh); + continue; + } + + if (sx + sw > vw) + sw = vw - sx; + if (sy + sh > vh) + sh = vh - sy; + + if (sw < 0) + sw = 0; + if (sh < 0) + sh = 0; + + if (!sw || !sh) + continue; + + sx -= abs(sr->dx); + sy -= abs(sr->dy); + sw += abs(sr->dx); + sh += abs(sr->dy); + ewk_view_repaint_add(sd->_priv, sx, sy, sw, sh); + INF("using repaint for inner frame scolling!"); + } + } + + return EINA_TRUE; +} + +static Eina_Bool _ewk_view_tiled_smart_repaints_process(Ewk_View_Smart_Data *sd) +{ + const Eina_Rectangle *pr, *pr_end; + size_t count; + int sx, sy; + + ewk_frame_scroll_pos_get(sd->main_frame, &sx, &sy); + + pr = ewk_view_repaints_get(sd->_priv, &count); + pr_end = pr + count; + for (; pr < pr_end; pr++) { + Eina_Rectangle r; + r.x = pr->x + sx; + r.y = pr->y + sy; + r.w = pr->w; + r.h = pr->h; + ewk_tiled_backing_store_update(sd->backing_store, &r); + } + ewk_tiled_backing_store_updates_process(sd->backing_store); + + return EINA_TRUE; +} + +static Eina_Bool _ewk_view_tiled_smart_contents_resize(Ewk_View_Smart_Data *sd, int w, int h) +{ + ewk_tiled_backing_store_contents_resize(sd->backing_store, w, h); + return EINA_TRUE; +} + +static Eina_Bool _ewk_view_tiled_smart_zoom_set(Ewk_View_Smart_Data *sd, float zoom, Evas_Coord cx, Evas_Coord cy) +{ + Evas_Coord x, y, w, h; + Eina_Bool r; + r = ewk_tiled_backing_store_zoom_set(sd->backing_store, + &zoom, cx, cy, &x, &y); + if (!r) + return r; + ewk_tiled_backing_store_disabled_update_set(sd->backing_store, EINA_TRUE); + r = _parent_sc.zoom_set(sd, zoom, cx, cy); + ewk_frame_scroll_set(sd->main_frame, -x, -y); + ewk_frame_scroll_size_get(sd->main_frame, &w, &h); + ewk_tiled_backing_store_fix_offsets(sd->backing_store, w, h); + ewk_view_scrolls_process(sd); + evas_object_smart_calculate(sd->backing_store); + evas_object_smart_calculate(sd->self); + ewk_tiled_backing_store_disabled_update_set(sd->backing_store, EINA_FALSE); + return r; +} + +static Eina_Bool _ewk_view_tiled_smart_zoom_weak_set(Ewk_View_Smart_Data *sd, float zoom, Evas_Coord cx, Evas_Coord cy) +{ + return ewk_tiled_backing_store_zoom_weak_set(sd->backing_store, zoom, cx, cy); +} + +static void _ewk_view_tiled_smart_zoom_weak_smooth_scale_set(Ewk_View_Smart_Data *sd, Eina_Bool smooth_scale) +{ + ewk_tiled_backing_store_zoom_weak_smooth_scale_set(sd->backing_store, smooth_scale); +} + +static void _ewk_view_tiled_smart_flush(Ewk_View_Smart_Data *sd) +{ + ewk_tiled_backing_store_flush(sd->backing_store); + _parent_sc.flush(sd); +} + +static Eina_Bool _ewk_view_tiled_smart_pre_render_region(Ewk_View_Smart_Data *sd, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h, float zoom) +{ + return ewk_tiled_backing_store_pre_render_region + (sd->backing_store, x, y, w, h, zoom); +} + +static Eina_Bool _ewk_view_tiled_smart_pre_render_relative_radius(Ewk_View_Smart_Data *sd, unsigned int n, float zoom) +{ + return ewk_tiled_backing_store_pre_render_relative_radius + (sd->backing_store, n, zoom); +} + +static void _ewk_view_tiled_smart_pre_render_cancel(Ewk_View_Smart_Data *sd) +{ + ewk_tiled_backing_store_pre_render_cancel(sd->backing_store); +} + +static Eina_Bool _ewk_view_tiled_smart_disable_render(Ewk_View_Smart_Data *sd) +{ + return ewk_tiled_backing_store_disable_render(sd->backing_store); +} + +static Eina_Bool _ewk_view_tiled_smart_enable_render(Ewk_View_Smart_Data *sd) +{ + return ewk_tiled_backing_store_enable_render(sd->backing_store); +} + +/** + * Sets the smart class api using tiled backing store, enabling view + * to be inherited. + * + * @param api class definition to be set, all members with the + * exception of Evas_Smart_Class->data may be overridden. Must + * @b not be @c NULL. + * + * @note Evas_Smart_Class->data is used to implement type checking and + * is not supposed to be changed/overridden. If you need extra + * data for your smart class to work, just extend + * Ewk_View_Smart_Class instead. + * + * @return @c EINA_TRUE on success, @c EINA_FALSE on failure (probably + * version mismatch). + * + * @see ewk_view_base_smart_set() + */ +Eina_Bool ewk_view_tiled_smart_set(Ewk_View_Smart_Class *api) +{ + if (!ewk_view_base_smart_set(api)) + return EINA_FALSE; + + if (EINA_UNLIKELY(!_parent_sc.sc.add)) + ewk_view_base_smart_set(&_parent_sc); + + api->sc.add = _ewk_view_tiled_smart_add; + + api->backing_store_add = _ewk_view_tiled_smart_backing_store_add; + api->scrolls_process = _ewk_view_tiled_smart_scrolls_process; + api->repaints_process = _ewk_view_tiled_smart_repaints_process; + api->contents_resize = _ewk_view_tiled_smart_contents_resize; + api->zoom_set = _ewk_view_tiled_smart_zoom_set; + api->zoom_weak_set = _ewk_view_tiled_smart_zoom_weak_set; + api->zoom_weak_smooth_scale_set = _ewk_view_tiled_smart_zoom_weak_smooth_scale_set; + api->flush = _ewk_view_tiled_smart_flush; + api->pre_render_region = _ewk_view_tiled_smart_pre_render_region; + api->pre_render_relative_radius = _ewk_view_tiled_smart_pre_render_relative_radius; + api->pre_render_cancel = _ewk_view_tiled_smart_pre_render_cancel; + api->disable_render = _ewk_view_tiled_smart_disable_render; + api->enable_render = _ewk_view_tiled_smart_enable_render; + return EINA_TRUE; +} + +static inline Evas_Smart *_ewk_view_tiled_smart_class_new(void) +{ + static Ewk_View_Smart_Class api = EWK_VIEW_SMART_CLASS_INIT_NAME_VERSION("EWK_View_Tiled"); + static Evas_Smart *smart = 0; + + if (EINA_UNLIKELY(!smart)) { + ewk_view_tiled_smart_set(&api); + smart = evas_smart_class_new(&api.sc); + } + + return smart; +} + +/** + * Creates a new EFL WebKit View object using tiled backing store. + * + * View objects are the recommended way to deal with EFL WebKit as it + * abstracts the complex pieces of the process. + * + * This object is almost the same as the one returned by the ewk_view_add() + * function, but it uses the tiled backing store instead of the default + * backing store. + * + * @param e canvas where to create the view object. + * + * @return view object or @c NULL if errors. + * + * @see ewk_view_uri_set() + */ +Evas_Object *ewk_view_tiled_add(Evas *e) +{ + return evas_object_smart_add(e, _ewk_view_tiled_smart_class_new()); +} + +/** + * Get the cache of unused tiles used by this view. + * + * @param o view object to get cache. + * @return instance of "cache of unused tiles" or @c NULL on errors. + */ +Ewk_Tile_Unused_Cache *ewk_view_tiled_unused_cache_get(const Evas_Object *o) +{ + Ewk_View_Smart_Data *sd = ewk_view_smart_data_get(o); + EINA_SAFETY_ON_NULL_RETURN_VAL(sd, 0); + return ewk_tiled_backing_store_tile_unused_cache_get(sd->backing_store); +} + +/** + * Set the cache of unused tiles used by this view. + * + * @param o view object to get cache. + * @param cache instance of "cache of unused tiles". This can be used + * to share a single cache amongst different views. The tiles + * from one view will not be used by the other! This is just to + * limit the group with amount of unused memory. + * If @c NULL is provided, then a new cache is created. + */ +void ewk_view_tiled_unused_cache_set(Evas_Object *o, Ewk_Tile_Unused_Cache *cache) +{ + Ewk_View_Smart_Data *sd = ewk_view_smart_data_get(o); + EINA_SAFETY_ON_NULL_RETURN(sd); + ewk_tiled_backing_store_tile_unused_cache_set(sd->backing_store, cache); +} + +/** + * Set the function with the same name of the tiled backing store. + * @param o the tiled backing store object. + * @param flag value of the tiled backing store flag to set. + */ +void ewk_view_tiled_process_entire_queue_set(Evas_Object *o, Eina_Bool flag) +{ + Ewk_View_Smart_Data *sd = ewk_view_smart_data_get(o); + EINA_SAFETY_ON_NULL_RETURN(sd); + ewk_tiled_backing_store_process_entire_queue_set(sd->backing_store, flag); +} |