jpeg pushed a commit to branch master. http://git.enlightenment.org/core/efl.git/commit/?id=73919ea43708b4caa980ca59c3d4a2fa8864aef2
commit 73919ea43708b4caa980ca59c3d4a2fa8864aef2 Author: Jean-Philippe Andre <jp.an...@samsung.com> Date: Thu Nov 13 10:47:29 2014 +0900 Evas masking: Implement mask support in evas_render This implements supports for masking inside evas_render, which means: - Render the mask itself into a surface (ALPHA if possible) - Pass this mask surface to the draw context - Apply mask recursively in case a masked object is contained by another masked object. @feature --- src/lib/evas/canvas/evas_render.c | 343 +++++++++++++++++++++++++++++++++++-- src/lib/evas/include/evas_inline.x | 39 ++++- 2 files changed, 362 insertions(+), 20 deletions(-) diff --git a/src/lib/evas/canvas/evas_render.c b/src/lib/evas/canvas/evas_render.c index 4a340ee..896a330 100644 --- a/src/lib/evas/canvas/evas_render.c +++ b/src/lib/evas/canvas/evas_render.c @@ -255,6 +255,7 @@ _evas_render_cur_clip_cache_del(Evas_Public_Data *e, Evas_Object_Protected_Data y + e->framespace.y, w, h); } +/* sets the redraw flag for all the proxies depending on this obj as a source */ static void _evas_proxy_redraw_set(Evas_Public_Data *e, Evas_Object_Protected_Data *obj, Eina_Bool render) @@ -293,6 +294,59 @@ _evas_proxy_redraw_set(Evas_Public_Data *e, Evas_Object_Protected_Data *obj, } } +/* sets the mask redraw flag for all the objects clipped by this mask */ +static void +_evas_mask_redraw_set(Evas_Public_Data *e EINA_UNUSED, + Evas_Object_Protected_Data *obj) +{ + Evas_Object_Protected_Data *clippee; + Eina_List *l; + + if (!(obj->mask->redraw && + obj->mask->x == obj->cur->geometry.x && + obj->mask->y == obj->cur->geometry.y && + obj->mask->w == obj->cur->geometry.w && + obj->mask->h == obj->cur->geometry.h)) + { + EINA_COW_WRITE_BEGIN(evas_object_mask_cow, obj->mask, + Evas_Object_Mask_Data, mask) + mask->redraw = EINA_TRUE; + mask->x = obj->cur->geometry.x; + mask->y = obj->cur->geometry.y; + mask->w = obj->cur->geometry.w; + mask->h = obj->cur->geometry.h; + EINA_COW_WRITE_END(evas_object_mask_cow, obj->mask, mask); + } + + if (!obj->cur->cache.clip.dirty) + { + EINA_COW_STATE_WRITE_BEGIN(obj, state_write, cur) + state_write->cache.clip.dirty = EINA_TRUE; + EINA_COW_STATE_WRITE_END(obj, state_write, cur); + } + + EINA_LIST_FOREACH(obj->clip.clipees, l, clippee) + { + evas_object_clip_recalc(clippee); + } +} + +static inline Eina_Bool +_evas_render_object_changed_get(Evas_Object_Protected_Data *obj) +{ + if (obj->smart.smart) + return evas_object_smart_changed_get(obj->object); + else + return obj->changed; +} + +static inline Eina_Bool +_evas_render_object_is_mask(Evas_Object_Protected_Data *obj) +{ + if (!obj) return EINA_FALSE; + return (obj->mask->is_mask && obj->clip.clipees); +} + static void _evas_render_phase1_direct(Evas_Public_Data *e, Eina_Array *active_objects, @@ -302,7 +356,6 @@ _evas_render_phase1_direct(Evas_Public_Data *e, { unsigned int i; Evas_Object *eo_obj; - Eina_Bool changed; RD(" [--- PHASE 1 DIRECT\n"); for (i = 0; i < active_objects->count; i++) @@ -311,13 +364,19 @@ _evas_render_phase1_direct(Evas_Public_Data *e, eina_array_data_get(active_objects, i); if (obj->changed) evas_object_clip_recalc(obj); - if (!obj->proxy->proxies && !obj->proxy->proxy_textures) continue; - - if (obj->smart.smart) - changed = evas_object_smart_changed_get(obj->object); - else changed = obj->changed; - if (changed) _evas_proxy_redraw_set(e, obj, EINA_FALSE); + if (obj->proxy->proxies || obj->proxy->proxy_textures) + { + /* is proxy source */ + if (_evas_render_object_changed_get(obj)) + _evas_proxy_redraw_set(e, obj, EINA_FALSE); + } + if (_evas_render_object_is_mask(obj)) + { + /* is image clipper */ + if (_evas_render_object_changed_get(obj)) + _evas_mask_redraw_set(e, obj); + } } for (i = 0; i < render_objects->count; i++) { @@ -336,11 +395,14 @@ _evas_render_phase1_direct(Evas_Public_Data *e, { evas_object_clip_recalc(obj); obj->func->render_pre(eo_obj, obj, obj->private_data); - if (obj->proxy->redraw) + + if (obj->proxy->redraw || obj->mask->redraw) _evas_render_prev_cur_clip_cache_add(e, obj); - if (obj->proxy->proxies || obj->proxy->proxy_textures) + + if (!obj->smart.smart || evas_object_smart_changed_get(eo_obj)) { - if (!obj->smart.smart || evas_object_smart_changed_get(eo_obj)) + /* proxy sources */ + if (obj->proxy->proxies || obj->proxy->proxy_textures) { EINA_COW_WRITE_BEGIN(evas_object_proxy_cow, obj->proxy, Evas_Object_Proxy_Data, proxy_write) @@ -349,6 +411,10 @@ _evas_render_phase1_direct(Evas_Public_Data *e, proxy_write); _evas_proxy_redraw_set(e, obj, EINA_TRUE); } + + /* clipper objects (image masks) */ + if (_evas_render_object_is_mask(obj)) + _evas_mask_redraw_set(e, obj); } RD(" pre-render-done smart:%p|%p [%p, %i] | [%p, %i] has_map:%i had_map:%i\n", @@ -547,6 +613,7 @@ _evas_render_phase1_object_process(Evas_Public_Data *e, Evas_Object *eo_obj, } else { + /* non smart object */ if ((!obj->clip.clipees) && _evas_render_is_relevant(eo_obj)) { if (is_active) @@ -573,6 +640,20 @@ _evas_render_phase1_object_process(Evas_Public_Data *e, Evas_Object *eo_obj, RD(" skip - not smart, not active or clippees or not relevant\n"); } } + else if (is_active && _evas_render_object_is_mask(obj) && + (evas_object_is_visible(eo_obj, obj) || evas_object_was_visible(eo_obj, obj))) + { + if (obj->restack) + OBJ_ARRAY_PUSH(restack_objects, obj); + else + { + OBJ_ARRAY_PUSH(render_objects, obj); + obj->render_pre = EINA_TRUE; + } + + RDI(level); + RD(" relevant + active: clipper image\n"); + } else { RDI(level); @@ -582,6 +663,7 @@ _evas_render_phase1_object_process(Evas_Public_Data *e, Evas_Object *eo_obj, } else { + /* not changed */ RD(" not changed... [%i] -> (%i %i %p %i) [%i]\n", evas_object_is_visible(eo_obj, obj), obj->cur->visible, obj->cur->cache.clip.visible, obj->smart.smart, @@ -615,6 +697,7 @@ _evas_render_phase1_object_process(Evas_Public_Data *e, Evas_Object *eo_obj, } else { + /* not smart */ if (evas_object_is_opaque(eo_obj, obj) && evas_object_is_visible(eo_obj, obj)) { @@ -637,6 +720,14 @@ _evas_render_phase1_object_process(Evas_Public_Data *e, Evas_Object *eo_obj, } } } + else if (is_active && _evas_render_object_is_mask(obj) && + evas_object_is_visible(eo_obj, obj)) + { + RDI(level); + RD(" visible clipper image\n"); + OBJ_ARRAY_PUSH(render_objects, obj); + obj->render_pre = EINA_TRUE; + } /* else if (obj->smart.smart) { RDI(level); @@ -1139,7 +1230,11 @@ evas_render_mapped(Evas_Public_Data *e, Evas_Object *eo_obj, if (mapped) { - if (proxy_src_clip) + if (_evas_render_object_is_mask(obj)) + { + // don't return; + } + else if (proxy_src_clip) { if ((!evas_object_is_visible(eo_obj, obj)) || (obj->clip.clipees) || (obj->cur->have_clipees)) @@ -1395,6 +1490,7 @@ evas_render_mapped(Evas_Public_Data *e, Evas_Object *eo_obj, obj->cur->bounding_box.x, obj->cur->bounding_box.x, obj->cur->bounding_box.w, obj->cur->bounding_box.h); #endif + if (mapped) { RDI(level); @@ -1431,11 +1527,31 @@ evas_render_mapped(Evas_Public_Data *e, Evas_Object *eo_obj, if (obj->cur->clipper) { - if (_evas_render_has_map(eo_obj, obj)) + if (_evas_render_has_map(eo_obj, obj) || + _evas_render_object_is_mask(obj->cur->clipper)) evas_object_clip_recalc(obj); _evas_render_mapped_context_clip_set(e, eo_obj, obj, ctx, proxy_render_data, - off_x, off_y); + off_x, off_y); + + /* Clipper masks */ + if (_evas_render_object_is_mask(obj->cur->clipper)) + { + // This path can be hit when we're multiplying masks on top of each other... + Evas_Object_Protected_Data *mask = + (Evas_Object_Protected_Data *) obj->cur->clipper; + if (mask->mask->redraw || !mask->mask->surface) + evas_render_mask_subrender(obj->layer->evas, mask, NULL); + + if (mask->mask->surface) + { + e->engine.func->context_clip_image_set + (e->engine.data.output, ctx, + mask->mask->surface, + mask->mask->x + off_x, + mask->mask->y + off_y); + } + } } obj->func->render(eo_obj, obj, obj->private_data, e->engine.data.output, ctx, @@ -1448,19 +1564,21 @@ evas_render_mapped(Evas_Public_Data *e, Evas_Object *eo_obj, { if (obj->cur->clipper) { + Evas_Object_Protected_Data *clipper = obj->cur->clipper; int x, y, w, h; - if (_evas_render_has_map(eo_obj, obj)) + if (_evas_render_has_map(eo_obj, obj) || + _evas_render_object_is_mask(obj->cur->clipper)) evas_object_clip_recalc(obj); x = obj->cur->cache.clip.x; y = obj->cur->cache.clip.y; w = obj->cur->cache.clip.w; h = obj->cur->cache.clip.h; RECTS_CLIP_TO_RECT(x, y, w, h, - obj->cur->clipper->cur->cache.clip.x, - obj->cur->clipper->cur->cache.clip.y, - obj->cur->clipper->cur->cache.clip.w, - obj->cur->clipper->cur->cache.clip.h); + clipper->cur->cache.clip.x, + clipper->cur->cache.clip.y, + clipper->cur->cache.clip.w, + clipper->cur->cache.clip.h); e->engine.func->context_clip_set(e->engine.data.output, context, x + off_x, y + off_y, w, h); @@ -1558,6 +1676,161 @@ evas_render_proxy_subrender(Evas *eo_e, Evas_Object *eo_source, Evas_Object *eo_ EINA_COW_WRITE_END(evas_object_proxy_cow, source->proxy, proxy_write); } +/* @internal + * Synchronously render a mask image (or smart object) into a surface. + * In SW the target surface will be ALPHA only (GRY8), after conversion. + * In GL the target surface will be RGBA for now. TODO: Find out how to + * render GL to alpha, if that's possible. + */ +void +evas_render_mask_subrender(Evas_Public_Data *evas, + Evas_Object_Protected_Data *mask, + Evas_Object_Protected_Data *prev_mask) +{ + int x, y, w, h, r, g, b, a; + void *ctx; + + if (!mask) return; + if (!mask->mask->redraw && mask->mask->surface) + { + DBG("Requested mask redraw but the redraw flag is off."); + return; + } + + x = mask->cur->geometry.x; + y = mask->cur->geometry.y; + w = mask->cur->geometry.w; + h = mask->cur->geometry.h; + + r = mask->cur->color.r; + g = mask->cur->color.g; + b = mask->cur->color.b; + a = mask->cur->color.a; + if ((r != 255) || (g != 255) || (b != 255) || (a != 255)) + { + EINA_COW_STATE_WRITE_BEGIN(mask, state_write, cur) + { + state_write->color.r = 255; + state_write->color.g = 255; + state_write->color.b = 255; + state_write->color.a = 255; + } + EINA_COW_STATE_WRITE_END(mask, state_write, cur); + } + + if (prev_mask == mask) + prev_mask = NULL; + + if (prev_mask) + { + if (!prev_mask->mask->is_mask) + { + ERR("Passed invalid mask that is not a mask"); + prev_mask = NULL; + } + else if (!prev_mask->mask->surface) + { + // FIXME? + WRN("Mask render order may be invalid"); + evas_render_mask_subrender(evas, prev_mask, NULL); + } + } + + EINA_COW_WRITE_BEGIN(evas_object_mask_cow, mask->mask, Evas_Object_Mask_Data, mdata) + mdata->redraw = EINA_FALSE; + + /* delete render surface if changed or if already alpha + * (we don't know how to render objects to alpha) */ + if (mdata->surface && ((w != mdata->w) || (h != mdata->h) || mdata->is_alpha)) + { + ENFN->image_map_surface_free(ENDT, mdata->surface); + mdata->surface = NULL; + } + + /* create new RGBA render surface if needed */ + if (!mdata->surface) + { + mdata->surface = ENFN->image_map_surface_new(ENDT, w, h, EINA_TRUE); + if (!mdata->surface) goto end; + mdata->w = w; + mdata->h = h; + } + + mdata->x = x; + mdata->y = y; + mdata->is_alpha = EINA_FALSE; + + /* Clear surface with transparency */ + ctx = ENFN->context_new(ENDT); + ENFN->context_color_set(ENDT, ctx, 0, 0, 0, 0); + ENFN->context_render_op_set(ENDT, ctx, EVAS_RENDER_COPY); + ENFN->rectangle_draw(ENDT, ctx, mdata->surface, 0, 0, w, h, EINA_FALSE); + ENFN->context_free(ENDT, ctx); + + /* Render mask to RGBA surface */ + ctx = ENFN->context_new(ENDT); + if (prev_mask) + { + ENFN->context_clip_image_set(ENDT, ctx, + prev_mask->mask->surface, + prev_mask->mask->x - x, + prev_mask->mask->y - y); + } + evas_render_mapped(evas, mask->object, mask, ctx, mdata->surface, + -x, -y, 1, 0, 0, evas->output.w, evas->output.h, + NULL, 1, EINA_TRUE, EINA_FALSE); + ENFN->context_free(ENDT, ctx); + + /* BEGIN HACK */ + + /* Now we want to convert this RGBA surface to Alpha. + * NOTE: So, this is not going to work with the GL engine but only with + * the SW engine. Here's the detection hack: + * FIXME: If you know of a way to support rendering to GL_ALPHA in GL, + * then we should render directly to an ALPHA surface. A priori, + * GLES FBO does not support this. + */ + if (!ENFN->gl_surface_read_pixels) + { + RGBA_Image *alpha_surface; + DATA32 *rgba; + DATA8* alpha; + + alpha_surface = ENFN->image_new_from_copied_data + (ENDT, w, h, NULL, EINA_TRUE, EVAS_COLORSPACE_GRY8); + if (!alpha_surface) goto end; + + /* Copy alpha channel */ + rgba = ((RGBA_Image *) mdata->surface)->image.data; + alpha = alpha_surface->image.data8; + for (y = h; y; --y) + for (x = w; x; --x, alpha++, rgba++) + *alpha = (DATA8) A_VAL(rgba); + + /* Now we can drop the original surface */ + ENFN->image_map_surface_free(ENDT, mdata->surface); + mdata->surface = alpha_surface; + mdata->is_alpha = EINA_TRUE; + } + + /* END OF HACK */ + +end: + EINA_COW_WRITE_END(evas_object_mask_cow, mask->mask, mdata); + + if ((r != 255) || (g != 255) || (b != 255) || (a != 255)) + { + EINA_COW_STATE_WRITE_BEGIN(mask, state_write, cur) + { + state_write->color.r = r; + state_write->color.g = g; + state_write->color.b = b; + state_write->color.a = a; + } + EINA_COW_STATE_WRITE_END(mask, state_write, cur); + } +} + static void _evas_render_cutout_add(Evas_Public_Data *e, Evas_Object_Protected_Data *obj, int off_x, int off_y) { @@ -1878,6 +2151,7 @@ evas_render_updates_internal(Evas *eo_e, if (UNLIKELY((evas_object_is_opaque(eo_obj, obj) || ((obj->func->has_opaque_rect) && (obj->func->has_opaque_rect(eo_obj, obj, obj->private_data)))) && + (!obj->mask->is_mask) && evas_object_is_visible(eo_obj, obj) && (!obj->clip.clipees) && (obj->cur->visible) && @@ -1999,6 +2273,9 @@ evas_render_updates_internal(Evas *eo_e, x = cx; y = cy; w = cw; h = ch; if (((w > 0) && (h > 0)) || (obj->is_smart)) { + Evas_Object_Protected_Data *prev_mask = NULL; + Evas_Object_Protected_Data *mask = NULL; + if (!obj->is_smart) { RECTS_CLIP_TO_RECT(x, y, w, h, @@ -2011,6 +2288,30 @@ evas_render_updates_internal(Evas *eo_e, e->engine.func->context_clip_set(e->engine.data.output, e->engine.data.context, x, y, w, h); + + /* Clipper masks */ + if (_evas_render_object_is_mask(obj->cur->clipper)) + mask = (Evas_Object_Protected_Data *) obj->cur->clipper; // main object clipped by this mask + else if (obj->cur->cache.clip.mask) + mask = (Evas_Object_Protected_Data *) obj->cur->cache.clip.mask; // propagated clip + prev_mask = (Evas_Object_Protected_Data *) obj->cur->cache.clip.prev_mask; + + if (mask) + { + if (mask->mask->redraw || !mask->mask->surface) + evas_render_mask_subrender(obj->layer->evas, mask, prev_mask); + + if (mask->mask->surface) + { + e->engine.func->context_clip_image_set + (e->engine.data.output, + e->engine.data.context, + mask->mask->surface, + mask->mask->x + off_x, + mask->mask->y + off_y); + } + } + #if 1 /* FIXME: this can slow things down... figure out optimum... coverage */ for (j = offset; j < e->temporary_objects.count; ++j) { @@ -2030,6 +2331,12 @@ evas_render_updates_internal(Evas *eo_e, do_async); e->engine.func->context_cutout_clear(e->engine.data.output, e->engine.data.context); + + if (mask) + { + e->engine.func->context_clip_image_unset + (e->engine.data.output, e->engine.data.context); + } } } } diff --git a/src/lib/evas/include/evas_inline.x b/src/lib/evas/include/evas_inline.x index 756a60f..0a86e9b 100644 --- a/src/lib/evas/include/evas_inline.x +++ b/src/lib/evas/include/evas_inline.x @@ -1,6 +1,8 @@ #ifndef EVAS_INLINE_H #define EVAS_INLINE_H +#include "evas_private.h" + static inline Eina_Bool _evas_render_has_map(Evas_Object *eo_obj, Evas_Object_Protected_Data *obj) { @@ -65,9 +67,13 @@ static inline int evas_object_is_opaque(Evas_Object *eo_obj, Evas_Object_Protected_Data *obj) { if (obj->is_smart) return 0; - /* If a mask: Assume alpha */ + /* If clipped: Assume alpha */ if (obj->cur->cache.clip.a == 255) { + /* If has mask image: Always assume non opaque */ + if ((obj->cur->clipper && obj->cur->clipper->mask->is_mask) || + (obj->cur->cache.clip.mask)) + return 0; if (obj->func->is_opaque) return obj->func->is_opaque(eo_obj, obj, obj->private_data); return 1; @@ -240,6 +246,7 @@ evas_object_clip_recalc(Evas_Object_Protected_Data *obj) Evas_Object_Protected_Data *clipper = NULL; int cx, cy, cw, ch, cr, cg, cb, ca; int nx, ny, nw, nh, nr, ng, nb, na; + const Evas_Object_Protected_Data *mask = NULL, *prev_mask = NULL; Eina_Bool cvis, nvis; Evas_Object *eo_obj; @@ -293,6 +300,29 @@ evas_object_clip_recalc(Evas_Object_Protected_Data *obj) RECTS_CLIP_TO_RECT(cx, cy, cw, ch, nx, ny, nw, nh); } + if (clipper->mask->is_mask) + { + // Set complex masks the object being clipped (parent) + mask = clipper; + + // Forward any mask from the parents + if (EINA_LIKELY(obj->smart.parent != NULL)) + { + Evas_Object_Protected_Data *parent = + eo_data_scope_get(obj->smart.parent, EVAS_OBJECT_CLASS); + if (parent->cur->cache.clip.mask) + { + if (parent->cur->cache.clip.mask != mask) + prev_mask = parent->cur->cache.clip.mask; + } + } + } + else if (clipper->cur->cache.clip.mask) + { + // Pass complex masks to children + mask = clipper->cur->cache.clip.mask; + } + nvis = clipper->cur->cache.clip.visible; nr = clipper->cur->cache.clip.r; ng = clipper->cur->cache.clip.g; @@ -304,6 +334,7 @@ evas_object_clip_recalc(Evas_Object_Protected_Data *obj) cb = (cb * (nb + 1)) >> 8; ca = (ca * (na + 1)) >> 8; } + if ((ca == 0 && obj->cur->render_op == EVAS_RENDER_BLEND) || (cw <= 0) || (ch <= 0)) cvis = EINA_FALSE; @@ -316,7 +347,9 @@ evas_object_clip_recalc(Evas_Object_Protected_Data *obj) obj->cur->cache.clip.g == cg && obj->cur->cache.clip.b == cb && obj->cur->cache.clip.a == ca && - obj->cur->cache.clip.dirty == EINA_FALSE) + obj->cur->cache.clip.dirty == EINA_FALSE && + obj->cur->cache.clip.mask == mask && + obj->cur->cache.clip.prev_mask == prev_mask) return ; EINA_COW_STATE_WRITE_BEGIN(obj, state_write, cur) @@ -331,6 +364,8 @@ evas_object_clip_recalc(Evas_Object_Protected_Data *obj) state_write->cache.clip.b = cb; state_write->cache.clip.a = ca; state_write->cache.clip.dirty = EINA_FALSE; + state_write->cache.clip.mask = mask; + state_write->cache.clip.prev_mask = prev_mask; } EINA_COW_STATE_WRITE_END(obj, state_write, cur); } --