#define EVAS_FILTER_PROTECTED #include "evas_common_private.h" #include "evas_private.h" #include "../../lib/efl/interfaces/efl_gfx_filter.eo.h" #include "evas_filter.eo.h" #include "evas_filter.h" #define MY_CLASS EVAS_FILTER_MIXIN #define ENFN obj->layer->evas->engine.func #define ENDT obj->layer->evas->engine.data.output #define FCOW_BEGIN(_pd) ({ Evas_Object_Filter_Data *_fcow = eina_cow_write(evas_object_filter_cow, (const Eina_Cow_Data**)&(_pd->data)); _state_check(_fcow); _fcow; }) #define FCOW_END(_fcow, _pd) eina_cow_done(evas_object_filter_cow, (const Eina_Cow_Data**)&(_pd->data), _fcow, EINA_TRUE) typedef struct _Evas_Filter_Data Evas_Filter_Data; typedef struct _Evas_Filter_Post_Render_Data Evas_Filter_Post_Render_Data; struct _Evas_Filter_Data { const Evas_Object_Filter_Data *data; Eina_Bool has_cb; SLK(lck); Eina_List *post_data; }; struct _Evas_Filter_Post_Render_Data { Evas_Filter_Context *ctx; Eina_Bool success; }; static inline void _state_check(Evas_Object_Filter_Data *fcow) { if (!fcow->state.cur.name) fcow->state.cur.name = eina_stringshare_add("default"); if (!fcow->state.next.name) fcow->state.next.name = eina_stringshare_add("default"); } static void _filter_end_sync(Evas_Filter_Context *ctx, Evas_Object_Protected_Data *obj, Evas_Filter_Data *pd, Eina_Bool success) { void *previous = pd->data->output; Eo *eo_obj = obj->object; #ifdef DEBUG EINA_SAFETY_ON_FALSE_RETURN(eina_main_loop_is()); #endif if (!success) { ERR("Filter failed at runtime!"); evas_filter_invalid_set(eo_obj, EINA_TRUE); evas_filter_dirty(eo_obj); } else { Evas_Object_Filter_Data *fcow; void *output = evas_filter_buffer_backing_steal(ctx, EVAS_FILTER_BUFFER_OUTPUT_ID); fcow = FCOW_BEGIN(pd); fcow->output = output; FCOW_END(fcow, pd); } // Destroy context as we won't reuse it. evas_filter_buffer_backing_release(ctx, previous); evas_filter_context_destroy(ctx); } static Eina_Bool _render_post_cb(void *data, const Eo_Event *event EINA_UNUSED) { Eo *eo_obj = data; Evas_Object_Protected_Data *obj = eo_data_scope_get(eo_obj, EVAS_OBJECT_CLASS); Evas_Filter_Data *pd = eo_data_scope_get(eo_obj, MY_CLASS); Eina_List *post_data; Evas_Filter_Post_Render_Data *task; SLKL(pd->lck); post_data = pd->post_data; pd->post_data = NULL; SLKU(pd->lck); EINA_LIST_FREE(post_data, task) { _filter_end_sync(task->ctx, obj, pd, task->success); free(task); } return EO_CALLBACK_CONTINUE; } static void _filter_cb(Evas_Filter_Context *ctx, void *data, Eina_Bool success) { Eo *eo_obj = data; Evas_Object_Protected_Data *obj = eo_data_scope_get(eo_obj, EVAS_OBJECT_CLASS); Evas_Filter_Data *pd = eo_data_scope_get(eo_obj, MY_CLASS); Evas_Filter_Post_Render_Data *post_data; if (!pd->data->async) { _filter_end_sync(ctx, obj, pd, success); return; } post_data = calloc(1, sizeof(*post_data)); post_data->success = success; post_data->ctx = ctx; SLKL(pd->lck); pd->post_data = eina_list_append(pd->post_data, post_data); SLKU(pd->lck); } static void _filter_source_hash_free_cb(void *data) { Evas_Filter_Proxy_Binding *pb = data; Evas_Object_Protected_Data *proxy, *source; Evas_Filter_Data *pd; proxy = eo_data_scope_get(pb->eo_proxy, EVAS_OBJECT_CLASS); source = eo_data_scope_get(pb->eo_source, EVAS_OBJECT_CLASS); if (source) { EINA_COW_WRITE_BEGIN(evas_object_proxy_cow, source->proxy, Evas_Object_Proxy_Data, source_write) source_write->proxies = eina_list_remove(source_write->proxies, pb->eo_proxy); EINA_COW_WRITE_END(evas_object_proxy_cow, source->proxy, source_write) } pd = eo_data_scope_get(pb->eo_proxy, MY_CLASS); if (pd && proxy) { if (!eina_hash_population(pd->data->sources)) { EINA_COW_WRITE_BEGIN(evas_object_proxy_cow, proxy->proxy, Evas_Object_Proxy_Data, proxy_write) proxy_write->is_proxy = EINA_FALSE; EINA_COW_WRITE_END(evas_object_proxy_cow, source->proxy, proxy_write) } } eina_stringshare_del(pb->name); free(pb); } Eina_Bool evas_filter_object_render(Eo *eo_obj, Evas_Object_Protected_Data *obj, void *output, void *context, void *surface, int x, int y, Eina_Bool do_async, Eina_Bool alpha) { Evas_Filter_Data *pd = eo_data_scope_get(eo_obj, MY_CLASS); if (!pd->data->invalid && (pd->data->chain || pd->data->code)) { int X, Y, W, H, l = 0, r = 0, t = 0, b = 0; Evas_Filter_Context *filter; void *drawctx; Eina_Bool ok; void *previous = pd->data->output; Evas_Object_Filter_Data *fcow; /* NOTE: Filter rendering is now done ENTIRELY on CPU. * So we rely on cache/cache2 to allocate a real image buffer, * that we can draw to. The OpenGL texture will be created only * after the rendering has been done, as we simply push the output * image to GL. */ W = obj->cur->geometry.w; H = obj->cur->geometry.h; X = obj->cur->geometry.x; Y = obj->cur->geometry.y; // Prepare color multiplier ENFN->context_color_set(output, context, obj->cur->cache.clip.r, obj->cur->cache.clip.g, obj->cur->cache.clip.b, obj->cur->cache.clip.a); if (obj->cur->clipper) ENFN->context_multiplier_set(output, context, obj->cur->clipper->cur->cache.clip.r, obj->cur->clipper->cur->cache.clip.g, obj->cur->clipper->cur->cache.clip.b, obj->cur->clipper->cur->cache.clip.a); else ENFN->context_multiplier_unset(output, context); if (!pd->data->chain) { Evas_Filter_Program *pgm; pgm = evas_filter_program_new(pd->data->name, alpha); evas_filter_program_source_set_all(pgm, pd->data->sources); evas_filter_program_data_set_all(pgm, pd->data->data); evas_filter_program_state_set(pgm, eo_obj, obj, pd->data->state.cur.name, pd->data->state.cur.value, pd->data->state.next.name, pd->data->state.next.value, pd->data->state.pos); if (!evas_filter_program_parse(pgm, pd->data->code)) { ERR("Filter program parsing failed"); evas_filter_program_del(pgm); if (!pd->data->invalid) { fcow = FCOW_BEGIN(pd); fcow->invalid = EINA_TRUE; FCOW_END(fcow, pd); } return EINA_FALSE; } fcow = FCOW_BEGIN(pd); fcow->chain = pgm; fcow->invalid = EINA_FALSE; FCOW_END(fcow, pd); } else if (previous && !pd->data->changed) { Eina_Bool redraw; redraw = evas_filter_program_state_set(pd->data->chain, eo_obj, obj, pd->data->state.cur.name, pd->data->state.cur.value, pd->data->state.next.name, pd->data->state.next.value, pd->data->state.pos); if (redraw) DBG("Filter redraw by state change!"); else if (obj->changed) { // FIXME: Check that something else than X,Y changed! redraw = EINA_TRUE; DBG("Filter redraw by object content change!"); } // Scan proxies to find if any changed if (!redraw && pd->data->sources) { Evas_Filter_Proxy_Binding *pb; Evas_Object_Protected_Data *source; Eina_Iterator *iter; iter = eina_hash_iterator_data_new(pd->data->sources); EINA_ITERATOR_FOREACH(iter, pb) { source = eo_data_scope_get(pb->eo_source, EVAS_OBJECT_CLASS); if (source->changed) { redraw = EINA_TRUE; break; } } eina_iterator_free(iter); } if (!redraw) { if (pd->has_cb) { // Post render callback is not required anymore Evas *e = obj->layer->evas->evas; eo_event_callback_del(e, EVAS_CANVAS_EVENT_RENDER_POST, _render_post_cb, eo_obj); pd->has_cb = EINA_FALSE; } // Render this image only ENFN->image_draw(ENDT, context, surface, previous, 0, 0, W, H, // src X + x, Y + y, W, H, // dst EINA_FALSE, // smooth do_async); return EINA_TRUE; } } else evas_filter_program_state_set(pd->data->chain, eo_obj, obj, pd->data->state.cur.name, pd->data->state.cur.value, pd->data->state.next.name, pd->data->state.next.value, pd->data->state.pos); filter = evas_filter_context_new(obj->layer->evas, do_async); // Run script ok = evas_filter_context_program_use(filter, pd->data->chain); if (!filter || !ok) { ERR("Parsing failed?"); evas_filter_context_destroy(filter); if (!pd->data->invalid) { fcow = FCOW_BEGIN(pd); fcow->invalid = EINA_TRUE; FCOW_END(fcow, pd); } return EINA_FALSE; } // Proxies evas_filter_context_proxy_render_all(filter, eo_obj, EINA_FALSE); // Draw Context drawctx = ENFN->context_new(ENDT); ENFN->context_color_set(ENDT, drawctx, 255, 255, 255, 255); // Allocate all buffers now evas_filter_context_buffers_allocate_all(filter); evas_filter_target_set(filter, context, surface, X + x, Y + y); // Request rendering from the object itself (child class) evas_filter_program_padding_get(pd->data->chain, &l, &r, &t, &b); ok = evas_filter_input_render(eo_obj, filter, drawctx, l, r, t, b, do_async); if (!ok) ERR("Filter input render failed."); ENFN->context_free(ENDT, drawctx); // Add post-run callback and run filter if (do_async && !pd->has_cb) { Evas *e = obj->layer->evas->evas; eo_event_callback_add(e, EVAS_CANVAS_EVENT_RENDER_POST, _render_post_cb, eo_obj); pd->has_cb = EINA_TRUE; } evas_filter_context_post_run_callback_set(filter, _filter_cb, eo_obj); ok = evas_filter_run(filter); fcow = FCOW_BEGIN(pd); fcow->changed = EINA_FALSE; fcow->async = do_async; if (!ok) fcow->invalid = EINA_TRUE; FCOW_END(fcow, pd); if (ok) { DBG("Effect rendering done."); return EINA_TRUE; } else { ERR("Rendering failed."); return EINA_FALSE; } } return EINA_FALSE; } EOLIAN static void _evas_filter_efl_gfx_filter_filter_program_set(Eo *eo_obj, Evas_Filter_Data *pd, const char *code, const char *name) { Evas_Object_Protected_Data *obj = eo_data_scope_get(eo_obj, EVAS_OBJECT_CLASS); Evas_Filter_Program *pgm = NULL; Evas_Object_Filter_Data *fcow; Eina_Bool alpha; if (!pd) return; if ((pd->data->code == code) && (!name || (pd->data->name == name))) return; if (pd->data->code && code && !strcmp(code, pd->data->code) && pd->data->name && name && !strcmp(name, pd->data->name)) return; evas_object_async_block(obj); fcow = FCOW_BEGIN(pd); { // Parse filter program evas_filter_program_del(fcow->chain); eina_stringshare_replace(&fcow->name, name); if (code) { alpha = evas_filter_input_alpha(eo_obj); pgm = evas_filter_program_new(fcow->name, alpha); evas_filter_program_source_set_all(pgm, fcow->sources); evas_filter_program_data_set_all(pgm, fcow->data); evas_filter_program_state_set(pgm, eo_obj, obj, fcow->state.cur.name, fcow->state.cur.value, fcow->state.next.name, fcow->state.next.value, fcow->state.pos); if (!evas_filter_program_parse(pgm, code)) { ERR("Parsing failed!"); evas_filter_program_del(pgm); pgm = NULL; } } fcow->chain = pgm; fcow->changed = EINA_TRUE; fcow->invalid = (pgm == NULL); eina_stringshare_replace(&fcow->code, code); } FCOW_END(fcow, pd); evas_filter_dirty(eo_obj); } EOLIAN static void _evas_filter_efl_gfx_filter_filter_program_get(Eo *eo_obj EINA_UNUSED, Evas_Filter_Data *pd, const char **code, const char **name) { if (code) *code = pd->data->code; if (name) *name = pd->data->name; } EOLIAN static void _evas_filter_efl_gfx_filter_filter_source_set(Eo *eo_obj, Evas_Filter_Data *pd, const char *name, Efl_Gfx_Base *eo_source) { Evas_Object_Protected_Data *obj = eo_data_scope_get(eo_obj, EVAS_OBJECT_CLASS); Evas_Filter_Proxy_Binding *pb, *pb_old = NULL; Evas_Object_Protected_Data *source = NULL; Evas_Object_Filter_Data *fcow = NULL; if (eo_source) source = eo_data_scope_get(eo_source, EVAS_OBJECT_CLASS); evas_object_async_block(obj); if (!name) { if (!eo_source || !pd->data->sources) return; if (eina_hash_del_by_data(pd->data->sources, eo_source)) goto update; return; } if (!source && !pd->data->sources) return; if (pd->data->sources) { pb_old = eina_hash_find(pd->data->sources, name); if (pb_old && (pb_old->eo_source == eo_source)) return; } fcow = FCOW_BEGIN(pd); if (!fcow->sources) fcow->sources = eina_hash_string_small_new(EINA_FREE_CB(_filter_source_hash_free_cb)); else if (pb_old) eina_hash_del(fcow->sources, name, pb_old); if (!source) { pb_old = eina_hash_find(fcow->sources, name); if (!pb_old) { FCOW_END(fcow, pd); return; } eina_hash_del_by_key(fcow->sources, name); goto update; } pb = calloc(1, sizeof(*pb)); pb->eo_proxy = eo_obj; pb->eo_source = eo_source; pb->name = eina_stringshare_add(name); if (!eina_list_data_find(source->proxy->proxies, eo_obj)) { EINA_COW_WRITE_BEGIN(evas_object_proxy_cow, source->proxy, Evas_Object_Proxy_Data, source_write) source_write->proxies = eina_list_append(source_write->proxies, eo_obj); EINA_COW_WRITE_END(evas_object_proxy_cow, source->proxy, source_write) } if (!obj->proxy->is_proxy) { EINA_COW_WRITE_BEGIN(evas_object_proxy_cow, obj->proxy, Evas_Object_Proxy_Data, proxy_write) proxy_write->is_proxy = EINA_TRUE; EINA_COW_WRITE_END(evas_object_proxy_cow, obj->proxy, proxy_write) } eina_hash_add(fcow->sources, pb->name, pb); evas_filter_program_source_set_all(fcow->chain, fcow->sources); evas_filter_program_data_set_all(fcow->chain, fcow->data); // Update object update: if (fcow) { fcow->changed = EINA_TRUE; fcow->invalid = EINA_FALSE; FCOW_END(fcow, pd); } evas_filter_dirty(eo_obj); } EOLIAN static Efl_Gfx_Base * _evas_filter_efl_gfx_filter_filter_source_get(Eo *obj EINA_UNUSED, Evas_Filter_Data *pd, const char * name) { Evas_Filter_Proxy_Binding *pb = eina_hash_find(pd->data->sources, name); if (!pb) return NULL; return pb->eo_source; } EOLIAN static void _evas_filter_efl_gfx_filter_filter_state_set(Eo *eo_obj, Evas_Filter_Data *pd, const char *cur_state, double cur_val, const char *next_state, double next_val, double pos) { Evas_Object_Protected_Data *obj = eo_data_scope_get(eo_obj, EVAS_OBJECT_CLASS); evas_object_async_block(obj); if ((cur_state != pd->data->state.cur.name) || (cur_val != pd->data->state.cur.value) || (next_state != pd->data->state.next.name) || (next_val != pd->data->state.next.value) || (pos != pd->data->state.pos)) { Evas_Object_Filter_Data *fcow = FCOW_BEGIN(pd); fcow->changed = 1; eina_stringshare_replace(&fcow->state.cur.name, cur_state); fcow->state.cur.value = cur_val; eina_stringshare_replace(&fcow->state.next.name, next_state); fcow->state.next.value = next_val; fcow->state.pos = pos; FCOW_END(fcow, pd); if (pd->data->chain) { evas_filter_program_state_set(pd->data->chain, eo_obj, obj, pd->data->state.cur.name, pd->data->state.cur.value, pd->data->state.next.name, pd->data->state.next.value, pd->data->state.pos); } evas_filter_dirty(eo_obj); } } EOLIAN static void _evas_filter_efl_gfx_filter_filter_state_get(Eo *obj EINA_UNUSED, Evas_Filter_Data *pd, const char **cur_state, double *cur_val, const char **next_state, double *next_val, double *pos) { if (cur_state) *cur_state = pd->data->state.cur.name; if (cur_val) *cur_val = pd->data->state.cur.value; if (next_state) *next_state = pd->data->state.next.name; if (next_val) *next_val = pd->data->state.next.value; if (pos) *pos = pd->data->state.pos; } EOLIAN static void _evas_filter_efl_gfx_filter_filter_padding_get(Eo *eo_obj EINA_UNUSED, Evas_Filter_Data *pd, int *l, int *r, int *t, int *b) { if (!pd->data->chain) { if (l) *l = 0; if (r) *r = 0; if (t) *t = 0; if (b) *b = 0; return; } evas_filter_program_padding_get(pd->data->chain, l, r, t, b); } EOLIAN static void _evas_filter_filter_changed_set(Eo *eo_obj EINA_UNUSED, Evas_Filter_Data *pd, Eina_Bool val) { if ((evas_object_filter_cow_default != pd->data) && (pd->data->changed != val)) { Evas_Object_Filter_Data *fcow = FCOW_BEGIN(pd); fcow->changed = val; FCOW_END(fcow, pd); } } EOLIAN static void _evas_filter_filter_invalid_set(Eo *eo_obj EINA_UNUSED, Evas_Filter_Data *pd, Eina_Bool val) { if (pd->data->invalid != val) { Evas_Object_Filter_Data *fcow = FCOW_BEGIN(pd); fcow->invalid = val; FCOW_END(fcow, pd); } } EOLIAN static Eo_Base * _evas_filter_eo_base_constructor(Eo *eo_obj, Evas_Filter_Data *pd) { Eo *obj = NULL; obj = eo_constructor(eo_super(eo_obj, MY_CLASS)); pd->data = eina_cow_alloc(evas_object_filter_cow); SLKI(pd->lck); return obj; } EOLIAN static void _evas_filter_eo_base_destructor(Eo *eo_obj, Evas_Filter_Data *pd) { Evas_Object_Protected_Data *obj = eo_data_scope_get(eo_obj, EVAS_OBJECT_CLASS); Evas_Filter_Data_Binding *db; Eina_Inlist *il; if (!pd->data || (evas_object_filter_cow_default == pd->data)) goto finish; if (pd->data->output) { if (!pd->data->async) ENFN->image_free(ENDT, pd->data->output); else evas_unref_queue_image_put(obj->layer->evas, pd->data->output); } eina_hash_free(pd->data->sources); EINA_INLIST_FOREACH_SAFE(pd->data->data, il, db) { eina_stringshare_del(db->name); eina_stringshare_del(db->value); free(db); } evas_filter_program_del(pd->data->chain); eina_stringshare_del(pd->data->code); eina_stringshare_del(pd->data->state.cur.name); eina_stringshare_del(pd->data->state.next.name); finish: eina_cow_free(evas_object_filter_cow, (const Eina_Cow_Data **) &pd->data); if (pd->has_cb) { Evas *e = obj->layer->evas->evas; eo_event_callback_del(e, EVAS_CANVAS_EVENT_RENDER_POST, _render_post_cb, eo_obj); } SLKD(pd->lck); eo_destructor(eo_super(eo_obj, MY_CLASS)); } EOLIAN static void _evas_filter_efl_gfx_filter_filter_data_set(Eo *eo_obj, Evas_Filter_Data *pd, const char *name, const char *value, Eina_Bool execute) { Evas_Filter_Data_Binding *db, *found = NULL; Evas_Object_Filter_Data *fcow; EINA_SAFETY_ON_NULL_RETURN(pd->data); EINA_SAFETY_ON_NULL_RETURN(name); EINA_INLIST_FOREACH(pd->data->data, db) { if (!strcmp(name, db->name)) { if (db->execute == execute) { if ((value == db->value) || (value && db->value && !strcmp(value, db->value))) return; } found = db; break; } } fcow = FCOW_BEGIN(pd); { if (found) { // Note: we are keeping references to NULL values here. eina_stringshare_replace(&found->value, value); found->execute = execute; } else if (value) { db = calloc(1, sizeof(Evas_Filter_Data_Binding)); db->name = eina_stringshare_add(name); db->value = eina_stringshare_add(value); db->execute = execute; fcow->data = eina_inlist_append(fcow->data, EINA_INLIST_GET(db)); } evas_filter_program_data_set_all(fcow->chain, fcow->data); fcow->changed = 1; } FCOW_END(fcow, pd); evas_filter_dirty(eo_obj); } EOLIAN static void _evas_filter_efl_gfx_filter_filter_data_get(Eo *obj EINA_UNUSED, Evas_Filter_Data *pd, const char *name, const char **value, Eina_Bool *execute) { Evas_Filter_Data_Binding *db; if (!value && !execute) return; EINA_SAFETY_ON_NULL_RETURN(pd->data); EINA_INLIST_FOREACH(pd->data->data, db) { if (!strcmp(name, db->name)) { if (value) *value = db->value; if (execute) *execute = db->execute; return; } } if (value) *value = NULL; if (execute) *execute = EINA_FALSE; } EOLIAN static void * _evas_filter_filter_output_buffer_get(Eo *obj EINA_UNUSED, Evas_Filter_Data *pd) { return pd->data->output; } #include "evas_filter.eo.c"