evas: Improve blur filter perf by using 2 passes

By simply splitting X and Y blurs in two passes we can improve
the performance of the blur filter a lot.

There is still much to be done to make it really fast and nice
looking:
- implement true gaussian blur (not sine-based approximation,
  right now the actual blurs look different in SW and GL)
- exploit linear interpolation for R tap instead of R*2+1 taps
  (a tap being a texel fetch)
- downscale & upscale large images with large blur radii
This commit is contained in:
Jean-Philippe Andre 2017-02-07 18:48:46 +09:00
parent 5bce7120f1
commit b90246a619
10 changed files with 164 additions and 108 deletions

View File

@ -315,7 +315,7 @@ evas_filter_context_buffers_allocate_all(Evas_Filter_Context *ctx)
}
render |= (fb->id == EVAS_FILTER_BUFFER_INPUT_ID);
render |= fb->is_render;
render |= fb->is_render || fb->transient;
draw |= (fb->id == EVAS_FILTER_BUFFER_OUTPUT_ID);
fb->buffer = _ector_buffer_create(fb, render, draw);
@ -438,7 +438,11 @@ _command_new(Evas_Filter_Context *ctx, Evas_Filter_Mode mode,
cmd->draw.B = 255;
cmd->draw.A = 255;
cmd->draw.rop = EFL_GFX_RENDER_OP_BLEND;
if (output) output->dirty = EINA_TRUE;
if (output)
{
cmd->draw.output_was_dirty = output->dirty;
output->dirty = EINA_TRUE;
}
ctx->commands = eina_inlist_append(ctx->commands, EINA_INLIST_GET(cmd));
return cmd;
@ -535,6 +539,84 @@ evas_filter_command_fill_add(Evas_Filter_Context *ctx, void *draw_context,
return cmd;
}
static Evas_Filter_Command *
evas_filter_command_blur_add_gl(Evas_Filter_Context *ctx,
Evas_Filter_Buffer *in, Evas_Filter_Buffer *out,
Evas_Filter_Blur_Type type,
int dx, int dy, int ox, int oy, int count,
int R, int G, int B, int A)
{
Evas_Filter_Command *cmd;
Evas_Filter_Buffer *dx_out, *dy_in;
/* GL blur implementation:
* - Always split X and Y passes (only one pass if 1D blur)
* - TODO: Repeat blur for large radius
* - TODO: Scale down & up for cheap blur
* - The rest is all up to the engine!
*/
if (dx && dy)
{
dx_out = evas_filter_temporary_buffer_get(ctx, 0, 0, in->alpha_only, 1);
if (!dx_out) goto fail;
dy_in = dx_out;
}
else
{
dx_out = out;
dy_in = in;
}
if (dx)
{
XDBG("Add GL blur %d -> %d (%dx%d px)", in->id, dx_out->id, dx, 0);
cmd = _command_new(ctx, EVAS_FILTER_MODE_BLUR, in, NULL, dx_out);
if (!cmd) goto fail;
cmd->blur.type = type;
cmd->blur.dx = dx;
cmd->blur.count = count;
}
if (dy)
{
XDBG("Add GL blur %d -> %d (%dx%d px)", dy_in->id, out->id, 0, dy);
cmd = _command_new(ctx, EVAS_FILTER_MODE_BLUR, dy_in, NULL, out);
if (!cmd) goto fail;
cmd->blur.type = type;
cmd->blur.dy = dy;
cmd->blur.count = count;
}
cmd->draw.ox = ox;
cmd->draw.oy = oy;
DRAW_COLOR_SET(R, G, B, A);
cmd->draw.rop = (in == out) ? EFL_GFX_RENDER_OP_COPY : EFL_GFX_RENDER_OP_BLEND;
_filter_buffer_unlock_all(ctx);
return cmd;
fail:
ERR("Failed to add blur");
_filter_buffer_unlock_all(ctx);
return NULL;
}
static Eina_Bool
_blur_support_gl(Evas_Filter_Context *ctx, Evas_Filter_Buffer *in, Evas_Filter_Buffer *out)
{
Evas_Filter_Command cmd = {};
cmd.input = in;
cmd.output = out;
cmd.mode = EVAS_FILTER_MODE_BLUR;
cmd.ctx = ctx;
cmd.blur.type = EVAS_FILTER_BLUR_GAUSSIAN;
cmd.blur.dx = 5;
return cmd.ENFN->gfx_filter_supports(cmd.ENDT, &cmd) == EVAS_FILTER_SUPPORT_GL;
}
Evas_Filter_Command *
evas_filter_command_blur_add(Evas_Filter_Context *ctx, void *drawctx,
int inbuf, int outbuf, Evas_Filter_Blur_Type type,
@ -573,21 +655,8 @@ evas_filter_command_blur_add(Evas_Filter_Context *ctx, void *drawctx,
return _command_new(ctx, EVAS_FILTER_MODE_SKIP, NULL, NULL, NULL);
}
if (ctx->gl)
{
// GL engine: single pass!
XDBG("Add GL blur %d -> %d (%dx%d px)", in->id, out->id, dx, dy);
cmd = _command_new(ctx, EVAS_FILTER_MODE_BLUR, in, NULL, out);
if (!cmd) goto fail;
cmd->blur.type = type;
cmd->blur.dx = dx;
cmd->blur.dy = dy;
cmd->blur.count = count;
cmd->draw.ox = ox;
cmd->draw.oy = oy;
DRAW_COLOR_SET(R, G, B, A);
return cmd;
}
if (_blur_support_gl(ctx, in, out))
return evas_filter_command_blur_add_gl(ctx, in, out, type, dx, dy, ox, oy, count, R, G, B, A);
// Note (SW engine):
// The basic blur operation overrides the pixels in the target buffer,

View File

@ -223,6 +223,7 @@ struct _Evas_Filter_Command
Eina_Bool clip_use : 1;
Eina_Bool clip_mode_lrtb : 1;
Eina_Bool need_temp_buffer : 1;
Eina_Bool output_was_dirty : 1;
} draw;
};

View File

@ -245,8 +245,8 @@ enum _Shader_Type {
SHD_MAP,
SHD_FILTER_DISPLACE,
SHD_FILTER_CURVE,
SHD_FILTER_BLUR,
SHD_TYPE_LAST
SHD_FILTER_BLUR_X,
SHD_FILTER_BLUR_Y
};
#define ARRAY_BUFFER_USE 500
@ -651,7 +651,7 @@ void evas_gl_common_filter_displace_push(Evas_Engine_GL_Context *gc
int x, int y, int w, int h, double dx, double dy, Eina_Bool nearest);
void evas_gl_common_filter_curve_push(Evas_Engine_GL_Context *gc, Evas_GL_Texture *tex,
int x, int y, int w, int h, const uint8_t *points, int channel);
void evas_gl_common_filter_blur_push(Evas_Engine_GL_Context *gc, Evas_GL_Texture *tex, int x, int y, int w, int h, double dx, double dy);
void evas_gl_common_filter_blur_push(Evas_Engine_GL_Context *gc, Evas_GL_Texture *tex, int x, int y, int w, int h, double radius, Eina_Bool horiz);
int evas_gl_common_shader_program_init(Evas_GL_Shared *shared);
void evas_gl_common_shader_program_shutdown(Evas_GL_Shared *shared);

View File

@ -3431,7 +3431,7 @@ void
evas_gl_common_filter_blur_push(Evas_Engine_GL_Context *gc,
Evas_GL_Texture *tex,
int x, int y, int w, int h,
double dx, double dy)
double radius, Eina_Bool horiz)
{
double sx, sy, sw, sh, pw, ph;
double ox1, oy1, ox2, oy2, ox3, oy3, ox4, oy4;
@ -3442,6 +3442,7 @@ evas_gl_common_filter_blur_push(Evas_Engine_GL_Context *gc,
GLfloat *filter_data;
Eina_Bool blend = EINA_TRUE;
Eina_Bool smooth = EINA_TRUE;
Shader_Type type = horiz ? SHD_FILTER_BLUR_X : SHD_FILTER_BLUR_Y;
r = R_VAL(&gc->dc->mul.col);
g = G_VAL(&gc->dc->mul.col);
@ -3450,18 +3451,18 @@ evas_gl_common_filter_blur_push(Evas_Engine_GL_Context *gc,
if (gc->dc->render_op == EVAS_RENDER_COPY)
blend = EINA_FALSE;
prog = evas_gl_common_shader_program_get(gc, SHD_FILTER_BLUR, NULL, 0, r, g, b, a,
prog = evas_gl_common_shader_program_get(gc, type, NULL, 0, r, g, b, a,
w, h, w, h, smooth, tex, EINA_FALSE,
NULL, EINA_FALSE, EINA_FALSE, 0, 0,
NULL, &nomul, NULL);
_filter_data_flush(gc, prog);
EINA_SAFETY_ON_NULL_RETURN(prog);
pn = _evas_gl_common_context_push(SHD_FILTER_BLUR, gc, tex, NULL, prog,
pn = _evas_gl_common_context_push(type, gc, tex, NULL, prog,
x, y, w, h, blend, smooth,
0, 0, 0, 0, 0, EINA_FALSE);
gc->pipe[pn].region.type = SHD_FILTER_BLUR;
gc->pipe[pn].region.type = type;
gc->pipe[pn].shader.prog = prog;
gc->pipe[pn].shader.cur_tex = tex->pt->texture;
gc->pipe[pn].shader.cur_texm = 0;
@ -3489,12 +3490,10 @@ evas_gl_common_filter_blur_push(Evas_Engine_GL_Context *gc,
PIPE_GROW(gc, pn, 6);
// Set blur properties... TODO
_filter_data_prepare(gc, pn, prog, 2);
_filter_data_prepare(gc, pn, prog, 1);
filter_data = gc->pipe[pn].array.filter_data;
filter_data[0] = dx;
filter_data[1] = dy;
filter_data[2] = w;
filter_data[3] = h;
filter_data[0] = radius;
filter_data[1] = horiz ? w : h;
sx = 0;
sy = 0;
@ -3530,8 +3529,6 @@ evas_gl_common_filter_blur_push(Evas_Engine_GL_Context *gc,
if (!nomul)
PUSH_6_COLORS(pn, r, g, b, a);
shader_array_flush(gc);
}
// ----------------------------------------------------------------------------

View File

@ -43,8 +43,9 @@ typedef enum {
SHADER_FLAG_FILTER_DISPLACE = (1 << 21),
SHADER_FLAG_FILTER_CURVE = (1 << 22),
SHADER_FLAG_FILTER_BLUR = (1 << 23),
SHADER_FLAG_FILTER_DIR_Y = (1 << 24),
} Shader_Flag;
#define SHADER_FLAG_COUNT 24
#define SHADER_FLAG_COUNT 25
static const char *_shader_flags[SHADER_FLAG_COUNT] = {
"TEX",
@ -70,7 +71,8 @@ static const char *_shader_flags[SHADER_FLAG_COUNT] = {
"RGB_A_PAIR",
"FILTER_DISPLACE",
"FILTER_CURVE",
"FILTER_BLUR"
"FILTER_BLUR",
"FILTER_DIR_Y",
};
static Eina_Bool compiler_released = EINA_FALSE;
@ -787,9 +789,13 @@ evas_gl_common_shader_flags_get(Evas_GL_Shared *shared, Shader_Type type,
case SHD_FILTER_CURVE:
flags |= SHADER_FLAG_FILTER_CURVE;
break;
case SHD_FILTER_BLUR:
case SHD_FILTER_BLUR_X:
flags |= SHADER_FLAG_FILTER_BLUR;
break;
case SHD_FILTER_BLUR_Y:
flags |= SHADER_FLAG_FILTER_BLUR;
flags |= SHADER_FLAG_FILTER_DIR_Y;
break;
default:
CRI("Impossible shader type.");
return 0;

View File

@ -92,8 +92,7 @@ static const char fragment_glsl[] =
"uniform sampler2D tex_filter;\n"
"#endif\n"
"#ifdef SHD_FILTER_BLUR\n"
"varying vec2 blur_radius;\n"
"varying vec2 blur_divider;\n"
"varying vec2 blur_data;\n"
"#endif\n"
"// ----------------------------------------------------------------------------\n"
"#ifndef SHD_FILTER_BLUR\n"
@ -228,41 +227,28 @@ static const char fragment_glsl[] =
"}\n"
"void main()\n"
"{\n"
" float x, y, div_x, div_y;\n"
" float rx = blur_radius.x;\n"
" float ry = blur_radius.y;\n"
" float u, u_div, radius, diam;\n"
" vec4 acc = vec4(0.,0.,0.,0.);\n"
" vec4 c;\n"
" div_x = blur_divider.x;\n"
" div_y = blur_divider.y;\n"
" float diam_x = rx * 2.0 + 1.0;\n"
" float diam_y = ry * 2.0 + 1.0;\n"
" float div = 0.0;\n"
"#if 1\n"
" // This is completely insane... but renders properly :)\n"
" for (y = -ry; y <= ry; y += 8.0)\n"
" radius = blur_data.x;\n"
" u_div = blur_data.y;\n"
" diam = radius * 2.0 + 1.0;\n"
" for (u = -radius; u <= radius; u += 1.0)\n"
" {\n"
" float wy = (y + ry) / (diam_y - 1.0) * 6.0 - 3.0;\n"
" wy = (sin(wy + M_PI_2) + 1.0) / 2.0;\n"
" for (x = -rx; x <= rx; x += 8.0)\n"
" {\n"
" float wx = (x + rx) / (diam_x - 1.0) * 6.0 - 3.0;\n"
" wx = (sin(wx + M_PI_2) + 1.0) / 2.0;\n"
" vec4 px = fetch_pixel(x / div_x, y / div_y);\n"
" acc += px * wx * wy;\n"
" div += wx * wy;\n"
" }\n"
" }\n"
" float w = (u + radius) / (diam - 1.0) * 6.0 - 3.0;\n"
" w = (sin(w + M_PI_2) + 1.0) / 2.0;\n"
"#ifndef SHD_FILTER_DIR_Y\n"
" vec4 px = fetch_pixel(u / u_div, 0.0);\n"
"#else\n"
" vec4 px = fetch_pixel(0.0, 0.0);\n"
" div = 1.0;\n"
" acc += px;\n"
" vec4 px = fetch_pixel(0.0, u / u_div);\n"
"#endif\n"
" c = acc / div;\n"
" acc += px * w;\n"
" div += w;\n"
" }\n"
"#ifndef SHD_NOMUL\n"
" gl_FragColor = c * col;\n"
" gl_FragColor = (acc / div) * col;\n"
"#else\n"
" gl_FragColor = c;\n"
" gl_FragColor = (acc / div);\n"
"#endif\n"
"}\n"
"#endif // SHD_FILTER_BLUR\n";
@ -341,9 +327,7 @@ static const char vertex_glsl[] =
"/* Gfx Filters: blur */\n"
"#ifdef SHD_FILTER_BLUR\n"
"attribute vec2 filter_data_0;\n"
"attribute vec2 filter_data_1;\n"
"varying vec2 blur_radius;\n"
"varying vec2 blur_divider;\n"
"varying vec2 blur_data;\n"
"#endif\n"
"void main()\n"
"{\n"
@ -414,8 +398,7 @@ static const char vertex_glsl[] =
" displace_max = filter_data_2;\n"
"#endif\n"
"#ifdef SHD_FILTER_BLUR\n"
" blur_radius = filter_data_0;\n"
" blur_divider = filter_data_1;\n"
" blur_data = filter_data_0;\n"
"#endif\n"
"}\n";

View File

@ -89,8 +89,7 @@ uniform sampler2D tex_filter;
#endif
#ifdef SHD_FILTER_BLUR
varying vec2 blur_radius;
varying vec2 blur_divider;
varying vec2 blur_data;
#endif
// ----------------------------------------------------------------------------
@ -247,43 +246,33 @@ vec4 fetch_pixel(float ox, float oy)
void main()
{
float x, y, div_x, div_y;
float rx = blur_radius.x;
float ry = blur_radius.y;
float u, u_div, radius, diam;
vec4 acc = vec4(0.,0.,0.,0.);
vec4 c;
div_x = blur_divider.x;
div_y = blur_divider.y;
float diam_x = rx * 2.0 + 1.0;
float diam_y = ry * 2.0 + 1.0;
float div = 0.0;
// This is completely insane... but renders properly :)
for (y = -ry; y <= ry; y += 1.0)
radius = blur_data.x;
u_div = blur_data.y;
diam = radius * 2.0 + 1.0;
for (u = -radius; u <= radius; u += 1.0)
{
float wy = (y + ry) / (diam_y - 1.0) * 6.0 - 3.0;
wy = (sin(wy + M_PI_2) + 1.0) / 2.0;
float w = (u + radius) / (diam - 1.0) * 6.0 - 3.0;
w = (sin(w + M_PI_2) + 1.0) / 2.0;
for (x = -rx; x <= rx; x += 1.0)
{
float wx = (x + rx) / (diam_x - 1.0) * 6.0 - 3.0;
wx = (sin(wx + M_PI_2) + 1.0) / 2.0;
#ifndef SHD_FILTER_DIR_Y
vec4 px = fetch_pixel(u / u_div, 0.0);
#else
vec4 px = fetch_pixel(0.0, u / u_div);
#endif
vec4 px = fetch_pixel(x / div_x, y / div_y);
acc += px * wx * wy;
div += wx * wy;
}
acc += px * w;
div += w;
}
c = acc / div;
#ifndef SHD_NOMUL
gl_FragColor = c * col;
gl_FragColor = (acc / div) * col;
#else
gl_FragColor = c;
gl_FragColor = (acc / div);
#endif
}

View File

@ -81,9 +81,7 @@ varying vec2 displace_max;
/* Gfx Filters: blur */
#ifdef SHD_FILTER_BLUR
attribute vec2 filter_data_0;
attribute vec2 filter_data_1;
varying vec2 blur_radius;
varying vec2 blur_divider;
varying vec2 blur_data;
#endif
@ -167,7 +165,6 @@ void main()
#endif
#ifdef SHD_FILTER_BLUR
blur_radius = filter_data_0;
blur_divider = filter_data_1;
blur_data = filter_data_0;
#endif
}

View File

@ -6,6 +6,8 @@ _gl_filter_blur(Render_Engine_GL_Generic *re, Evas_Filter_Command *cmd)
Evas_Engine_GL_Context *gc;
Evas_GL_Image *image, *surface;
RGBA_Draw_Context *dc_save;
Eina_Bool horiz;
double radius;
int x, y, w, h;
DEBUG_TIME_BEGIN();
@ -35,11 +37,22 @@ _gl_filter_blur(Render_Engine_GL_Generic *re, Evas_Filter_Command *cmd)
else
gc->dc->render_op = _gfx_to_evas_render_op(cmd->draw.rop);
DBG("blur %d @%p -> %d @%p", cmd->input->id, cmd->input->buffer,
cmd->output->id, cmd->output->buffer);
if (cmd->blur.dx)
{
horiz = EINA_TRUE;
radius = cmd->blur.dx;
}
else
{
horiz = EINA_FALSE;
radius = cmd->blur.dy;
}
evas_gl_common_filter_blur_push(gc, image->tex, x, y, w, h,
cmd->blur.dx, cmd->blur.dy);
DBG("blur %d @%p -> %d @%p (%.0fpx %s)",
cmd->input->id, cmd->input->buffer, cmd->output->id, cmd->output->buffer,
radius, horiz ? "X" : "Y");
evas_gl_common_filter_blur_push(gc, image->tex, x, y, w, h, radius, horiz);
evas_common_draw_context_free(gc->dc);
gc->dc = dc_save;
@ -58,6 +71,7 @@ gl_filter_blur_func_get(Evas_Filter_Command *cmd)
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd, NULL);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->output, NULL);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->input, NULL);
EINA_SAFETY_ON_FALSE_RETURN_VAL((!cmd->blur.dx) ^ (!cmd->blur.dy), NULL);
return _gl_filter_blur;
}

View File

@ -421,7 +421,7 @@ eng_filter_blur_func_get(Evas_Filter_Command *cmd)
}
break;
default:
CRI("Unsupported blur type %d", cmd->blur.type);
ERR("Unsupported blur type %d", cmd->blur.type);
return NULL;
}