evas cutouts - quickly avoid huge per issues with large nos of cutouts

i found evas_common_draw_context_apply_cutouts() was procsessing 300+
cutouts and as it's O(n^2)/2 to try and merge adjacent rects for
cutouts this really performs like complete junk. we apply cutout rects
a LOT. this is not the best solution, but it's quick and much faster
than doing the clipouts which drop framerate to like 1-2fps or so in the
nasty case i say (tyls -m of photos in a dir with a 2160 high
terminal).

this figures out the target area to limit the count of rects
significantly so O(n^2) is far far better when n is now < 10 most of
the time. and for the few operations where it's a high value this now
uses qsort to speed up merges etc. etc.

@optimize
This commit is contained in:
Carsten Haitzler 2016-12-30 18:55:55 +09:00
parent 5a9c6d393a
commit b0530aba4f
9 changed files with 200 additions and 70 deletions

View File

@ -1805,21 +1805,26 @@ evas_object_text_render(Evas_Object *eo_obj,
(((int)object->sub.col.b) * (amul)) / 255, \ (((int)object->sub.col.b) * (amul)) / 255, \
(((int)object->sub.col.a) * (amul)) / 255); (((int)object->sub.col.a) * (amul)) / 255);
#define DRAW_TEXT(ox, oy) \ #define DRAW_TEXT(ox, oy) \
if ((o->font) && (it->text_props.len > 0)) \ if ((o->font) && (it->text_props.len > 0)) { \
evas_font_draw_async_check(obj, output, \ ENFN->context_cutout_target(output, context, \
context, \ obj->cur->geometry.x + x + sl + ox + it->x, \
surface, \ obj->cur->geometry.y + y + st + oy, \
o->font, \ it->w, it->h); \
evas_font_draw_async_check(obj, output, \
context, \
surface, \
o->font, \
obj->cur->geometry.x + x + sl + ox + it->x, \ obj->cur->geometry.x + x + sl + ox + it->x, \
obj->cur->geometry.y + y + st + oy + \ obj->cur->geometry.y + y + st + oy + \
(int) o->max_ascent, \ (int)o->max_ascent, \
obj->cur->geometry.w, \ obj->cur->geometry.w, \
obj->cur->geometry.h, \ obj->cur->geometry.h, \
obj->cur->geometry.w, \ obj->cur->geometry.w, \
obj->cur->geometry.h, \ obj->cur->geometry.h, \
&it->text_props, \ &it->text_props, \
do_async); do_async); \
}
if (o->has_filter) if (o->has_filter)
{ {

View File

@ -12862,13 +12862,18 @@ evas_object_textblock_render(Evas_Object *eo_obj EINA_UNUSED,
cr = nr; cg = ng; cb = nb; ca = na; \ cr = nr; cg = ng; cb = nb; ca = na; \
} }
#define DRAW_TEXT(ox, oy) \ #define DRAW_TEXT(ox, oy) \
if (ti->parent.format->font.font) \ if (ti->parent.format->font.font) { \
evas_font_draw_async_check(obj, output, context, surface, \ ENFN->context_cutout_target(output, context, \
obj->cur->geometry.x + ln->x - (ln->h * 4) + ti->parent.x + x + (ox) - 100, \
obj->cur->geometry.y + ln->par->y + ln->y - ln->h + y + (oy), \
ti->parent.w + (ln->h * 8), ln->h * 3); \
evas_font_draw_async_check(obj, output, context, surface, \
ti->parent.format->font.font, \ ti->parent.format->font.font, \
obj->cur->geometry.x + ln->x + ti->parent.x + x + (ox), \ obj->cur->geometry.x + ln->x + ti->parent.x + x + (ox), \
obj->cur->geometry.y + ln->par->y + ln->y + yoff + y + (oy), \ obj->cur->geometry.y + ln->par->y + ln->y + yoff + y + (oy), \
ti->parent.w, ti->parent.h, ti->parent.w, ti->parent.h, \ ti->parent.w, ti->parent.h, ti->parent.w, ti->parent.h, \
&ti->text_props, do_async); &ti->text_props, do_async); \
}
/* backing */ /* backing */
#define DRAW_RECT(ox, oy, ow, oh, or, og, ob, oa) \ #define DRAW_RECT(ox, oy, ow, oh, or, og, ob, oa) \
@ -12884,6 +12889,11 @@ evas_object_textblock_render(Evas_Object *eo_obj EINA_UNUSED,
nr / 255, ng / 255, nb / 255, na / 255); \ nr / 255, ng / 255, nb / 255, na / 255); \
cr = nr; cg = ng; cb = nb; ca = na; \ cr = nr; cg = ng; cb = nb; ca = na; \
} \ } \
ENFN->context_cutout_target(output, context, \
obj->cur->geometry.x + ln->x + x + (ox), \
obj->cur->geometry.y + ln->par->y + ln->y + y + (oy), \
(ow), \
(oh)); \
ENFN->rectangle_draw(output, \ ENFN->rectangle_draw(output, \
context, \ context, \
surface, \ surface, \

View File

@ -531,6 +531,9 @@ evas_object_textgrid_render(Evas_Object *eo_obj EINA_UNUSED,
ENFN->context_color_set(output, context, ENFN->context_color_set(output, context,
row->rects[xx].r, row->rects[xx].g, row->rects[xx].r, row->rects[xx].g,
row->rects[xx].b, row->rects[xx].a); row->rects[xx].b, row->rects[xx].a);
ENFN->context_cutout_target(output, context,
xp + row->rects[xx].x, yp,
row->rects[xx].w, h);
ENFN->rectangle_draw(output, context, surface, ENFN->rectangle_draw(output, context, surface,
xp + row->rects[xx].x, yp, xp + row->rects[xx].x, yp,
row->rects[xx].w, h, row->rects[xx].w, h,
@ -600,6 +603,9 @@ evas_object_textgrid_render(Evas_Object *eo_obj EINA_UNUSED,
} }
while (font == current_font); while (font == current_font);
ENFN->context_cutout_target(output, context,
xp - w, yp + o->ascent - h,
w * 3, h * 3);
async_unref = async_unref =
ENFN->multi_font_draw(output, context, surface, ENFN->multi_font_draw(output, context, surface,
current_font, current_font,
@ -639,6 +645,9 @@ evas_object_textgrid_render(Evas_Object *eo_obj EINA_UNUSED,
ENFN->context_color_set(output, context, ENFN->context_color_set(output, context,
r, g, b, a); r, g, b, a);
font = _textgrid_font_get(o, text->bold, text->italic); font = _textgrid_font_get(o, text->bold, text->italic);
ENFN->context_cutout_target(output, context,
tx - w, ty - h,
w * 3, h * 3);
evas_font_draw_async_check(obj, output, context, surface, evas_font_draw_async_check(obj, output, context, surface,
font, tx, ty, ww, hh, font, tx, ty, ww, hh,
ww, hh, props, do_async); ww, hh, props, do_async);
@ -651,6 +660,9 @@ evas_object_textgrid_render(Evas_Object *eo_obj EINA_UNUSED,
ENFN->context_color_set(output, context, ENFN->context_color_set(output, context,
row->lines[xx].r, row->lines[xx].g, row->lines[xx].r, row->lines[xx].g,
row->lines[xx].b, row->lines[xx].a); row->lines[xx].b, row->lines[xx].a);
ENFN->context_cutout_target(output, context,
xp + row->lines[xx].x, yp + row->lines[xx].y,
row->lines[xx].w, 1);
ENFN->rectangle_draw(output, context, surface, ENFN->rectangle_draw(output, context, surface,
xp + row->lines[xx].x, yp + row->lines[xx].y, xp + row->lines[xx].x, yp + row->lines[xx].y,
row->lines[xx].w, 1, row->lines[xx].w, 1,

View File

@ -2878,6 +2878,11 @@ evas_render_updates_internal_loop(Evas *eo_e, Evas_Public_Data *evas,
_evas_render_cutout_add(evas, context, obj2, off_x + fx, off_y + fy); _evas_render_cutout_add(evas, context, obj2, off_x + fx, off_y + fy);
} }
#endif #endif
ENFN->context_cutout_target(ENDT, context,
off_x + obj->cur->cache.clip.x,
off_y + obj->cur->cache.clip.y,
obj->cur->cache.clip.w,
obj->cur->cache.clip.h);
eina_evlog("-cutouts_add", obj->object, 0.0, NULL); eina_evlog("-cutouts_add", obj->object, 0.0, NULL);
clean_them |= evas_render_mapped(evas, eo_obj, obj, context, clean_them |= evas_render_mapped(evas, eo_obj, obj, context,
surface, off_x + fx, surface, off_x + fx,

View File

@ -34,6 +34,6 @@ EAPI void evas_common_draw_context_set_anti_alias (RGBA_D
EAPI void evas_common_draw_context_set_color_interpolation (RGBA_Draw_Context *dc, int color_space); EAPI void evas_common_draw_context_set_color_interpolation (RGBA_Draw_Context *dc, int color_space);
EAPI void evas_common_draw_context_set_render_op (RGBA_Draw_Context *dc, int op); EAPI void evas_common_draw_context_set_render_op (RGBA_Draw_Context *dc, int op);
EAPI void evas_common_draw_context_set_sli (RGBA_Draw_Context *dc, int y, int h); EAPI void evas_common_draw_context_set_sli (RGBA_Draw_Context *dc, int y, int h);
EAPI void evas_common_draw_context_target_set (RGBA_Draw_Context *dc, int x, int y, int w, int h);
#endif /* _EVAS_DRAW_H */ #endif /* _EVAS_DRAW_H */

View File

@ -590,39 +590,66 @@ evas_common_draw_context_cutout_split(Cutout_Rects *res, int idx, Cutout_Rect *s
#undef R_NEW #undef R_NEW
} }
EAPI void
evas_common_draw_context_target_set(RGBA_Draw_Context *dc, int x, int y, int w, int h)
{
dc->cutout_target.x = x;
dc->cutout_target.y = y;
dc->cutout_target.w = w;
dc->cutout_target.h = h;
}
static int
_srt_y(const void *d1, const void *d2)
{
const Cutout_Rect *r1 = d1, *r2 = d2;
if (r1->y == r2->y) return r1->x - r2->x;
return r1->y - r2->y;
}
static int
_srt_x(const void *d1, const void *d2)
{
const Cutout_Rect *r1 = d1, *r2 = d2;
if (r1->x == r2->x) return r1->y - r2->y;
return r1->x - r2->x;
}
EAPI Cutout_Rects * EAPI Cutout_Rects *
evas_common_draw_context_apply_cutouts(RGBA_Draw_Context *dc, Cutout_Rects *reuse) evas_common_draw_context_apply_cutouts(RGBA_Draw_Context *dc, Cutout_Rects *reuse)
{ {
Cutout_Rects *res = NULL; Cutout_Rects *res = NULL;
int i; int i, j, active, found = 0;
int j;
if (!dc->clip.use) return NULL; if (!dc->clip.use) return NULL;
if ((dc->clip.w <= 0) || (dc->clip.h <= 0)) return NULL; if ((dc->clip.w <= 0) || (dc->clip.h <= 0)) return NULL;
if (!reuse) res = evas_common_draw_context_cutouts_new();
if (!reuse)
{
res = evas_common_draw_context_cutouts_new();
}
else else
{ {
evas_common_draw_context_cutouts_free(reuse); evas_common_draw_context_cutouts_free(reuse);
res = reuse; res = reuse;
} }
// this avoids a nasty case of O(n^2)/2 below with lots of rectangles
// to merge so only do this merging if the number of rects is small enough
// not to blow out into insanity
evas_common_draw_context_cutouts_add(res, dc->clip.x, dc->clip.y, dc->clip.w, dc->clip.h); evas_common_draw_context_cutouts_add(res, dc->clip.x, dc->clip.y, dc->clip.w, dc->clip.h);
for (i = 0; i < dc->cutout.active; i++)
for (i = 0; i < dc->cutout.active; ++i)
{ {
/* Don't loop on the element just added to the list as they are already correctly clipped. */ if ((dc->cutout_target.w != 0) &&
int active = res->active; (!RECTS_INTERSECT(dc->cutout.rects[i].x, dc->cutout.rects[i].y,
dc->cutout.rects[i].w, dc->cutout.rects[i].h,
dc->cutout_target.x, dc->cutout_target.y,
dc->cutout_target.w, dc->cutout_target.h)))
continue;
// Don't loop on the element just added to the list as they are
// already correctly clipped.
active = res->active;
for (j = 0; j < active; ) for (j = 0; j < active; )
{ {
if (evas_common_draw_context_cutout_split(res, j, dc->cutout.rects + i)) if (evas_common_draw_context_cutout_split
++j; (res, j, dc->cutout.rects + i)) j++;
else else active--;
active--;
} }
} }
/* merge rects */ /* merge rects */
@ -630,66 +657,125 @@ evas_common_draw_context_apply_cutouts(RGBA_Draw_Context *dc, Cutout_Rects *reus
#define RJ res->rects[j] #define RJ res->rects[j]
if (res->active > 1) if (res->active > 1)
{ {
int found = 1; if (res->active > 5)
while (found)
{ {
found = 0; // fast path for larger numbers of rects to merge by using
// qsort to sort by y and x to limit the number of rects
// we have to walk as rects that have a different y cannot
// be merged anyway (or x).
qsort(res->rects, res->active, sizeof(res->rects[0]), _srt_y);
for (i = 0; i < res->active; i++) for (i = 0; i < res->active; i++)
{ {
if (RI.w == 0) continue; // skip empty rect
for (j = i + 1; j < res->active; j++) for (j = i + 1; j < res->active; j++)
{ {
/* skip empty rects we are removing */ if (RJ.y != RI.y) break; // new line, sorted thus skip
if (RJ.w == 0) continue; if (RJ.w == 0) continue; // skip empty rect
/* check if its same width, immediately above or below */ // if J is the same height (could be merged)
if ((RJ.w == RI.w) && (RJ.x == RI.x)) if (RJ.h == RI.h)
{ {
if ((RJ.y + RJ.h) == RI.y) /* above */ // if J is immediately to the right of I
if (RJ.x == (RI.x + RI.w))
{ {
RI.y = RJ.y; RI.w = (RJ.x + RJ.w) - RI.x; // expand RI
RI.h += RJ.h; RJ.w = 0; // invalidate
RJ.w = 0; found++;
found = 1;
}
else if ((RI.y + RI.h) == RJ.y) /* below */
{
RI.h += RJ.h;
RJ.w = 0;
found = 1;
} }
// since we sort y and THEN x, if height matches
// but it's not immediately adjacent, no more
// rects exists that can be merged
else break;
} }
/* check if its same height, immediately left or right */ }
else if ((RJ.h == RI.h) && (RJ.y == RI.y)) }
qsort(res->rects, res->active, sizeof(res->rects[0]), _srt_x);
for (i = 0; i < res->active; i++)
{
if (RI.w == 0) continue; // skip empty rect
for (j = i + 1; j < res->active; j++)
{
if (RJ.x != RI.x) break; // new line, sorted thus skip
if (RJ.w == 0) continue; // skip empty rect
// if J is the same height (could be merged)
if (RJ.w == RI.w)
{ {
if ((RJ.x + RJ.w) == RI.x) /* left */ // if J is immediately to the right of I
if (RJ.y == (RI.y + RI.h))
{ {
RI.x = RJ.x; RI.h = (RJ.y + RJ.h) - RI.y; // expand RI
RI.w += RJ.w; RJ.w = 0; // invalidate
RJ.w = 0; found++;
found = 1;
} }
else if ((RI.x + RI.w) == RJ.x) /* right */ // since we sort y and THEN x, if height matches
// but it's not immediately adjacent, no more
// rects exists that can be merged
else break;
}
}
}
}
else
{
// for a small number of rects, keep things simple as the count
// is small and big-o complexity isnt a problem yet
found = 1;
while (found)
{
found = 0;
for (i = 0; i < res->active; i++)
{
for (j = i + 1; j < res->active; j++)
{
// skip empty rects we are removing
if (RJ.w == 0) continue;
// check if its same width, immediately above or below
if ((RJ.w == RI.w) && (RJ.x == RI.x))
{ {
RI.w += RJ.w; if ((RJ.y + RJ.h) == RI.y) // above
RJ.w = 0; {
found = 1; RI.y = RJ.y;
RI.h += RJ.h;
RJ.w = 0;
found++;
}
else if ((RI.y + RI.h) == RJ.y) // below
{
RI.h += RJ.h;
RJ.w = 0;
found++;
}
}
// check if its same height, immediately left or right
else if ((RJ.h == RI.h) && (RJ.y == RI.y))
{
if ((RJ.x + RJ.w) == RI.x) // left
{
RI.x = RJ.x;
RI.w += RJ.w;
RJ.w = 0;
found++;
}
else if ((RI.x + RI.w) == RJ.x) // right
{
RI.w += RJ.w;
RJ.w = 0;
found++;
}
} }
} }
} }
} }
} }
/* Repack the cutout */ // Repack the cutout
j = 0; j = 0;
for (i = 0; i < res->active; i++) for (i = 0; i < res->active; i++)
{ {
if (RI.w == 0) continue; if (RI.w == 0) continue;
if (i != j) if (i != j) RJ = RI;
RJ = RI;
j++; j++;
} }
res->active = j; res->active = j;
return res;
} }
return res; return res;
} }

View File

@ -756,6 +756,9 @@ struct _RGBA_Draw_Context
Eina_Bool async : 1; Eina_Bool async : 1;
} clip; } clip;
Cutout_Rects cutout; Cutout_Rects cutout;
struct {
int x, y, w, h;
} cutout_target;
struct { struct {
Cutout_Rects *rects; Cutout_Rects *rects;
int used; int used;

View File

@ -1375,6 +1375,7 @@ struct _Evas_Func
int (*context_multiplier_get) (void *data, void *context, int *r, int *g, int *b, int *a); int (*context_multiplier_get) (void *data, void *context, int *r, int *g, int *b, int *a);
void (*context_cutout_add) (void *data, void *context, int x, int y, int w, int h); void (*context_cutout_add) (void *data, void *context, int x, int y, int w, int h);
void (*context_cutout_clear) (void *data, void *context); void (*context_cutout_clear) (void *data, void *context);
void (*context_cutout_target) (void *data, void *context, int x, int y, int w, int h);
void (*context_anti_alias_set) (void *data, void *context, unsigned char aa); void (*context_anti_alias_set) (void *data, void *context, unsigned char aa);
unsigned char (*context_anti_alias_get) (void *data, void *context); unsigned char (*context_anti_alias_get) (void *data, void *context);
void (*context_color_interpolation_set) (void *data, void *context, int color_space); void (*context_color_interpolation_set) (void *data, void *context, int color_space);

View File

@ -645,9 +645,16 @@ eng_context_cutout_add(void *data EINA_UNUSED, void *context, int x, int y, int
static void static void
eng_context_cutout_clear(void *data EINA_UNUSED, void *context) eng_context_cutout_clear(void *data EINA_UNUSED, void *context)
{ {
evas_common_draw_context_target_set(context, 0, 0, 0, 0);
evas_common_draw_context_clear_cutouts(context); evas_common_draw_context_clear_cutouts(context);
} }
static void
eng_context_cutout_target(void *data EINA_UNUSED, void *context, int x, int y, int w, int h)
{
evas_common_draw_context_target_set(context, x, y, w, h);
}
static void static void
eng_context_anti_alias_set(void *data EINA_UNUSED, void *context, unsigned char aa) eng_context_anti_alias_set(void *data EINA_UNUSED, void *context, unsigned char aa)
{ {
@ -4655,6 +4662,7 @@ static Evas_Func func =
eng_context_multiplier_get, eng_context_multiplier_get,
eng_context_cutout_add, eng_context_cutout_add,
eng_context_cutout_clear, eng_context_cutout_clear,
eng_context_cutout_target,
eng_context_anti_alias_set, eng_context_anti_alias_set,
eng_context_anti_alias_get, eng_context_anti_alias_get,
eng_context_color_interpolation_set, eng_context_color_interpolation_set,