efl/src/lib/evas/canvas/evas_render2.c

486 lines
17 KiB
C

#include "evas_render2.h"
//////////////////////////////////////////////////////////////////////////////
// this is the start of a rewrite of the evas rendering infra. first port of
// call is to just make it work and still support async rendering with no
// optimizations at all. once it WORKS properly start adding back
// optimizations one at a time very carefully until it is equivalent to where
// evas render was before, THEN... we can consider switching it on by default
// but until then it's off unless you set:
//
// export EVAS_RENDER2=1
//
// at runtime.
//////////////////////////////////////////////////////////////////////////////
// data types
//////////////////////////////////////////////////////////////////////////////
typedef struct _Update Update;
struct _Update
{
Eina_Rectangle *area;
void *surface;
};
typedef struct _Update_Info Update_Info;
struct _Update_Info
{
void *surface;
int ux, uy, uw, uh;
int cx, cy, cw, ch;
};
// funcs
//////////////////////////////////////////////////////////////////////////////
Eina_Bool _evas_render2_begin(Eo *eo_e, Eina_Bool make_updates, Eina_Bool do_draw, Eina_Bool do_async);
void _evas_render2_idle_flush(Eo *eo_e);
void _evas_render2_dump(Eo *eo_e);
void _evas_render2_wait(Eo *eo_e);
static void _evas_render2_cow_gc(Eina_Cow *cow, int max);
static void _evas_render2_cow_all_gc(int max);
static void _evas_render2_all_sync(void);
static void _evas_render2_wakeup_cb(void *target, Evas_Callback_Type type, void *event_info);
static void _evas_render2_wakeup_send(void *data);
static void _evas_render2_always_call(Eo *eo_e, Evas_Callback_Type type, void *event_info);
static void _evas_render2_updates_clean(Evas_Public_Data *e);
static void _evas_render2_stage_last(Eo *eo_e, Eina_Bool make_updates, Eina_Bool do_async);
static void _evas_render2_stage_explicit_updates(Evas_Public_Data *e);
static void _evas_render2_stage_main_render_prepare(Evas_Public_Data *e);
static void _evas_render2_stage_render_do(Evas_Public_Data *e, Eina_Bool do_async);
static void _evas_render2_stage_reset(Evas_Public_Data *e);
static void _evas_render2_stage_object_cleanup(Evas_Public_Data *e);
static void _evas_render2_th_render(void *data);
static void _evas_render2_end(Eo *eo_e);
// global data (for rendering only)
//////////////////////////////////////////////////////////////////////////////
static Eina_List *_rendering = NULL;
// actual helper/internal functions
//////////////////////////////////////////////////////////////////////////////
static void
_evas_render2_cow_gc(Eina_Cow *cow, int max)
{
// gc a single cow type up to max iter or if max <= 0, all of them
int i = 0;
while (eina_cow_gc(cow))
{
if (max < 1) continue;
i++;
if (i > max) break;
}
}
static void
_evas_render2_cow_all_gc(int max)
{
// gc all known cow types
_evas_render2_cow_gc(evas_object_proxy_cow, max);
_evas_render2_cow_gc(evas_object_map_cow, max);
_evas_render2_cow_gc(evas_object_image_pixels_cow, max);
_evas_render2_cow_gc(evas_object_image_load_opts_cow, max);
_evas_render2_cow_gc(evas_object_image_state_cow, max);
}
static void
_evas_render2_all_sync(void)
{
// wait for ALL canvases to stop rendering
Eo *eo_e;
if (!_rendering) return;
eo_e = eina_list_data_get(eina_list_last(_rendering));
_evas_render2_wait(eo_e);
}
static void
_evas_render2_wakeup_cb(void *target, Evas_Callback_Type type EINA_UNUSED, void *event_info EINA_UNUSED)
{
// in mainloop run the rendering end handler
Eo *eo_e = target;
_evas_render2_end(eo_e);
}
static void
_evas_render2_wakeup_send(void *data)
{
// pass an event to the mainloop async event handler in evas so mainloop
// runs wakeup_cb and not in any thread
evas_async_events_put(data, 0, NULL, _evas_render2_wakeup_cb);
}
static void
_evas_render2_always_call(Eo *eo_e, Evas_Callback_Type type, void *event_info)
{
int freeze_num = 0, i;
eo_do(eo_e, freeze_num = eo_event_freeze_count_get());
for (i = 0; i < freeze_num; i++) eo_do(eo_e, eo_event_thaw());
evas_event_callback_call(eo_e, type, event_info);
for (i = 0; i < freeze_num; i++) eo_do(eo_e, eo_event_freeze());
}
static void
_evas_render2_updates_clean(Evas_Public_Data *e)
{
void *u;
// clean out updates and tmp surfaces we were holding/tracking
EINA_LIST_FREE(e->render.updates, u) eina_rectangle_free(u);
}
static void
_evas_render2_stage_last(Eo *eo_e, Eina_Bool make_updates, Eina_Bool do_async)
{
Evas_Public_Data *e = eo_data_scope_get(eo_e, EVAS_CANVAS_CLASS);
Eina_List *ret_updates = NULL;
Evas_Event_Render_Post post;
// XXX:
// XXX: actually update screen from mainloop here if needed - eg software
// XXX: engine needs to xshmputimage here - engine func does this
// XXX:
// if we did do rendering flush output to target and call callbacks
if (e->render.updates)
{
Update *ru;
_evas_render2_always_call(eo_e, EVAS_CALLBACK_RENDER_FLUSH_PRE, NULL);
EINA_LIST_FREE(e->render.updates, ru)
{
/* punch rect out */
e->engine.func->output_redraws_next_update_push
(e->engine.data.output, ru->surface,
ru->area->x, ru->area->y, ru->area->w, ru->area->h,
EVAS_RENDER_MODE_ASYNC_END);
ret_updates = eina_list_append(ret_updates, ru->area);
evas_cache_image_drop(ru->surface);
free(ru);
}
if (do_async) post.updated_area = ret_updates;
else e->render.updates = ret_updates;
e->engine.func->output_flush(e->engine.data.output,
EVAS_RENDER_MODE_ASYNC_END);
_evas_render2_always_call(eo_e, EVAS_CALLBACK_RENDER_FLUSH_POST, NULL);
}
// clear our previous rendering stuff from the engine
e->engine.func->output_redraws_clear(e->engine.data.output);
// stop tracking canvas as being async rendered
_rendering = eina_list_remove(_rendering, eo_e);
e->rendering = EINA_FALSE;
// call the post render callback with info if appropriate
if (do_async)
_evas_render2_always_call(eo_e, EVAS_CALLBACK_RENDER_POST, &post);
else
_evas_render2_always_call(eo_e, EVAS_CALLBACK_RENDER_POST, NULL);
// if we don't want to keep updates after this
if (!make_updates) _evas_render2_updates_clean(e);
// clean out modules we don't need anymore
evas_module_clean();
}
static void
_evas_render2_stage_explicit_updates(Evas_Public_Data *e)
{
Eina_Rectangle *r;
Eina_List *l;
// XXX: should time this
// if the output size changed, add a full redraw
if ((e->output.changed) || (e->framespace.changed))
{
e->engine.func->output_resize(e->engine.data.output,
e->output.w, e->output.h);
e->engine.func->output_redraws_rect_add(e->engine.data.output, 0, 0,
e->output.w, e->output.h);
}
// if there are explicit update regions - add them
EINA_LIST_FREE(e->damages, r)
{
// if we didnt just do a full redraw if output changed
if ((!e->output.changed) && (!e->framespace.changed))
e->engine.func->output_redraws_rect_add(e->engine.data.output,
r->x, r->y, r->w, r->h);
eina_rectangle_free(r);
}
// remove obscures from rendering - we keep them around
EINA_LIST_FOREACH(e->obscures, l, r)
e->engine.func->output_redraws_rect_del(e->engine.data.output,
r->x, r->y, r->w, r->h);
}
static void
_evas_render2_stage_main_render_prepare(Evas_Public_Data *e)
{
// XXX:
// XXX: do any preparation work here that is needed for the render
// XXX: threads to do their work, but can't be done in a thread. this
// XXX: also includes doing the pose render and clear of change flag
// XXX:
printf("_evas_render2_stage_main_render_prepare %p\n", e);
}
static void
_evas_render2_object_render(Evas_Public_Data *e, Evas_Object_Protected_Data *obj, Update_Info *uinf, int l)
{
Evas_Object_Protected_Data *obj2;
Evas_Object *eo_obj = obj->object;
const Eina_Inlist *il;
if ((!evas_object_is_visible(eo_obj, obj)) ||
(obj->clip.clipees) || (obj->cur->have_clipees)) return;
il = evas_object_smart_members_get_direct(eo_obj);
if (il)
{
EINA_INLIST_FOREACH(il, obj2)
_evas_render2_object_render(e, obj2, uinf, l + 1);
}
else
{
void *ctx = e->engine.func->context_new(e->engine.data.output);
if (ctx)
{
int offx = uinf->cx - uinf->ux;
int offy = uinf->cy - uinf->uy;
int x, y, w, h;
x = obj->cur->cache.clip.x;
y = obj->cur->cache.clip.y;
w = obj->cur->cache.clip.w;
h = obj->cur->cache.clip.h;
e->engine.func->context_clip_set(e->engine.data.output,
ctx, x + offx, y + offy, w, h);
obj->func->render(eo_obj, obj, obj->private_data,
e->engine.data.output,
ctx, uinf->surface,
offx, offy,
EINA_FALSE);
e->engine.func->context_free(e->engine.data.output, ctx);
}
}
}
static void
_evas_render2_stage_render_do(Evas_Public_Data *e, Eina_Bool do_async EINA_UNUSED)
{
Update_Info uinf;
Eina_Bool alpha;
// XXX: actually render now (either in thread or in mainloop)
// XXX:
printf(" _evas_render2_stage_render_do %p\n", e);
alpha = e->engine.func->canvas_alpha_get(e->engine.data.output,
e->engine.data.context);
while ((uinf.surface =
e->engine.func->output_redraws_next_update_get
(e->engine.data.output,
&uinf.ux, &uinf.uy, &uinf.uw, &uinf.uh,
&uinf.cx, &uinf.cy, &uinf.cw, &uinf.ch)))
{
Update *ru = NULL;
Evas_Layer *lay;
Evas_Object_Protected_Data *obj;
// if the canvas has an alpha channel, we must fill the region with
// empty (0 value argb)
if (alpha)
{
void *ctx = e->engine.func->context_new(e->engine.data.output);
if (ctx)
{
e->engine.func->context_render_op_set
(e->engine.data.output, ctx, EVAS_RENDER_COPY);
e->engine.func->context_color_set
(e->engine.data.output, ctx, 0, 0, 0, 0);
e->engine.func->rectangle_draw(e->engine.data.output,
ctx, uinf.surface,
uinf.cx, uinf.cy,
uinf.cw, uinf.ch,
EINA_FALSE);
e->engine.func->context_free(e->engine.data.output, ctx);
}
}
EINA_INLIST_FOREACH(e->layers, lay)
{
EINA_INLIST_FOREACH(lay->objects, obj)
{
_evas_render2_object_render(e, obj, &uinf, 0);
}
}
ru = malloc(sizeof(*ru));
ru->surface = uinf.surface;
NEW_RECT(ru->area, uinf.ux, uinf.uy, uinf.uw, uinf.uh);
e->render.updates = eina_list_append(e->render.updates, ru);
evas_cache_image_ref(uinf.surface);
}
e->engine.func->output_redraws_clear(e->engine.data.output);
}
static void
_evas_render2_stage_reset(Evas_Public_Data *e)
{
// cleanup canvas state after a render
e->changed = EINA_FALSE;
e->viewport.changed = EINA_FALSE;
e->output.changed = EINA_FALSE;
e->framespace.changed = EINA_FALSE;
e->invalidate = EINA_FALSE;
}
static void
_evas_render2_stage_object_cleanup(Evas_Public_Data *e)
{
// cleanup objects no longer needed now they have been scanned
// XXX:
// XXX: delete objects no longer needed here
// XXX:
printf("_evas_render2_stage_object_cleanup %p\n", e);
}
static void
_evas_render2_th_render(void *data)
{
Evas_Public_Data *e = data;
printf(".....................................................%p\n", e);
_evas_render2_stage_render_do(e, EINA_TRUE);
printf("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^%p\n", e);
}
// major functions (called from evas_render.c)
//////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
// BEGIN RENDERING (in mainloop)
///////////////////////////////////////////////////////////////////////
Eina_Bool
_evas_render2_begin(Eo *eo_e, Eina_Bool make_updates,
Eina_Bool do_draw, Eina_Bool do_async)
{
Evas_Public_Data *e = eo_data_scope_get(eo_e, EVAS_CANVAS_CLASS);
// if nothing changed at all since last render - skip this frame
if (!e->changed) return EINA_FALSE;
// we are still rendering while being asked to render - skip this frame
if (e->rendering && do_async) return EINA_FALSE;
// check viewport size is same as output - not allowed to differ
if ((e->output.w != e->viewport.w) || (e->output.h != e->viewport.h))
ERR("viewport size != output size!");
// we have to calculate smare objects before render so do that here
evas_call_smarts_calculate(eo_e);
// call canvas callbacks saying we are in the pre-render state
_evas_render2_always_call(eo_e, EVAS_CALLBACK_RENDER_PRE, NULL);
// begin out actual rendering bits
_evas_render2_stage_generate_object_updates(e);
_evas_render2_stage_explicit_updates(e);
// we are actually asked to draw not just go through the motions for gc
if (do_draw)
{
// now go through any preparation that needs doing in the mainloop
_evas_render2_stage_main_render_prepare(e);
// send off rendering to primary thread renderer
if (do_async)
{
// ref the canvas so it stays while threads work
eo_ref(eo_e);
// track canvas in list of things going in the background
e->rendering = EINA_TRUE;
_rendering = eina_list_append(_rendering, eo_e);
// queue the render thread command
evas_thread_cmd_enqueue(_evas_render2_th_render, e);
// flush the thread queue and call wakeup_send in the thread
evas_thread_queue_flush(_evas_render2_wakeup_send, eo_e);
}
// or if not async, do rendering inline now
else _evas_render2_stage_render_do(e, EINA_FALSE);
}
// reset flags since rendering is processed now
_evas_render2_stage_reset(e);
// clean/delete/gc objects here
_evas_render2_stage_object_cleanup(e);
// if we are not going to be async then do last render stage here
if (!do_async) _evas_render2_stage_last(eo_e, make_updates, EINA_FALSE);
if (!do_draw) _evas_render2_updates_clean(e);
evas_module_clean();
return EINA_TRUE;
}
///////////////////////////////////////////////////////////////////////
// END RENDERING (in mainloop)
///////////////////////////////////////////////////////////////////////
static void
_evas_render2_end(Eo *eo_e)
{
// this is actually called if rendering was async and is done. this is
// run in the mainloop where rendering began and may handle any cleanup
// or pixel upload if needed here
_evas_render2_stage_last(eo_e, EINA_TRUE, EINA_TRUE);
// release canvas object ref
eo_unref(eo_e);
}
///////////////////////////////////////////////////////////////////////
// IDLE FLUSH (in mainloop)
///////////////////////////////////////////////////////////////////////
void
_evas_render2_idle_flush(Eo *eo_e)
{
Evas_Public_Data *e = eo_data_scope_get(eo_e, EVAS_CANVAS_CLASS);
// wait for rendering to finish so we don't mess up shared resources
_evas_render2_wait(eo_e);
// clean fonts
evas_fonts_zero_pressure(eo_e);
// call engine idle flush call
if ((e->engine.func) && (e->engine.func->output_idle_flush) &&
(e->engine.data.output))
e->engine.func->output_idle_flush(e->engine.data.output);
// mark as invalidated
e->invalidate = EINA_TRUE;
// garbage collect up to 500 cow segments from our cow types
// not e that we should probably expose a call to do this outside of evas
// so ecore evas can call it in an idler
_evas_render2_cow_all_gc(500);
}
///////////////////////////////////////////////////////////////////////
// DUMP DATA (in mainloop)
///////////////////////////////////////////////////////////////////////
void
_evas_render2_dump(Eo *eo_e)
{
Evas_Public_Data *e = eo_data_scope_get(eo_e, EVAS_CANVAS_CLASS);
// freeze core cache system async work
evas_cache_async_freeze();
// wait for all canvases to render as they may share data we are dumping
_evas_render2_all_sync();
// go through idle flush first
_evas_render2_idle_flush(eo_e);
// also now tell engine to dump too
if ((e->engine.func) && (e->engine.func->output_dump) &&
(e->engine.data.output))
e->engine.func->output_dump(e->engine.data.output);
// clean up all cow sections no matter how many
_evas_render2_cow_all_gc(0);
// unfreeze core cache system
evas_cache_async_thaw();
}
///////////////////////////////////////////////////////////////////////
// WAIT ON CANVAS RENDER (if async, in mainloop)
///////////////////////////////////////////////////////////////////////
void
_evas_render2_wait(Eo *eo_e)
{
Evas_Public_Data *e = eo_data_scope_get(eo_e, EVAS_CANVAS_CLASS);
while (e->rendering) evas_async_events_process_blocking();
}