evas filters: Switch to uniforms in GL blur

This was a poor attempt at improving the performance but
obviously the root cause isn't fixed (too many texel fetches).

Uniform should (theoretically) work better than an attribute
the for loop. Just a guess here.

This also makes GL blur use a float value as radius, allowing
future extension to non-integer blur radii, as well as using
linear scaling as a fast blur approximation.
This commit is contained in:
Jean-Philippe Andre 2017-03-28 14:44:17 +09:00
parent 408115cef6
commit 06a7beec52
10 changed files with 144 additions and 93 deletions

View File

@ -660,8 +660,8 @@ evas_filter_command_blur_add_gl(Evas_Filter_Context *ctx,
int R, int G, int B, int A)
{
Evas_Filter_Command *cmd;
Evas_Filter_Buffer *dx_in, *dx_out, *dy_in, *tmp = NULL;
int dx, dy;
Evas_Filter_Buffer *dx_in, *dx_out, *dy_in, *dy_out, *tmp = NULL;
double dx, dy;
/* GL blur implementation:
* - Create intermediate buffer T (variable size)
@ -677,16 +677,18 @@ evas_filter_command_blur_add_gl(Evas_Filter_Context *ctx,
dx = rx;
dy = ry;
dx_in = in;
dy_out = out;
#if 0
if (type == EVAS_FILTER_BLUR_DEFAULT)
{
int down_x = 1, down_y = 1;
/* For now, disable scaling - testing perfect gaussian blur until it's
* ready:
* ready: */
down_x = MAX((1 << evas_filter_smallest_pow2_larger_than(dx / 2) / 2), 1);
down_y = MAX((1 << evas_filter_smallest_pow2_larger_than(dy / 2) / 2), 1);
*/
tmp = evas_filter_temporary_buffer_get(ctx, ctx->w / down_x, ctx->h / down_y,
in->alpha_only, EINA_TRUE);
@ -701,11 +703,17 @@ evas_filter_command_blur_add_gl(Evas_Filter_Context *ctx,
if (!cmd) goto fail;
cmd->draw.fillmode = EVAS_FILTER_FILL_MODE_STRETCH_XY;
dx_in = tmp;
tmp = evas_filter_temporary_buffer_get(ctx, ctx->w / down_x, ctx->h / down_y,
in->alpha_only, EINA_TRUE);
if (!tmp) goto fail;
dy_out = tmp;
}
#endif
if (dx && dy)
{
tmp = evas_filter_temporary_buffer_get(ctx, 0, 0, in->alpha_only, 1);
tmp = evas_filter_temporary_buffer_get(ctx, dx_in->w, dx_in->h, in->alpha_only, 1);
if (!tmp) goto fail;
dy_in = dx_out = tmp;
}
@ -717,7 +725,7 @@ evas_filter_command_blur_add_gl(Evas_Filter_Context *ctx,
if (dx)
{
XDBG("Add GL blur %d -> %d (%dx%d px)", dx_in->id, dx_out->id, dx, 0);
XDBG("Add GL blur %d -> %d (%.2fx%.2f px)", dx_in->id, dx_out->id, dx, 0.0);
cmd = _command_new(ctx, EVAS_FILTER_MODE_BLUR, dx_in, NULL, dx_out);
if (!cmd) goto fail;
cmd->blur.type = type;
@ -727,14 +735,23 @@ evas_filter_command_blur_add_gl(Evas_Filter_Context *ctx,
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);
XDBG("Add GL blur %d -> %d (%.2fx%.2f px)", dy_in->id, dy_out->id, 0.0, dy);
cmd = _command_new(ctx, EVAS_FILTER_MODE_BLUR, dy_in, NULL, dy_out);
if (!cmd) goto fail;
cmd->blur.type = type;
cmd->blur.dy = dy;
cmd->blur.count = count;
}
if (cmd->output != out)
{
XDBG("Add GL upscale %d (%dx%d) -> %d (%dx%d)",
cmd->output->id, cmd->output->w, cmd->output->h, out->id, out->w, out->h);
cmd = _command_new(ctx, EVAS_FILTER_MODE_BLEND, cmd->output, NULL, out);
if (!cmd) goto fail;
cmd->draw.fillmode = EVAS_FILTER_FILL_MODE_STRETCH_XY;
}
cmd->draw.ox = ox;
cmd->draw.oy = oy;
DRAW_COLOR_SET(R, G, B, A);

View File

@ -182,7 +182,7 @@ struct _Evas_Filter_Command
{
struct
{
int dx, dy;
float dx, dy;
int count;
Evas_Filter_Blur_Type type;
Eina_Bool auto_count : 1; // If true, BOX blur will be smooth using

View File

@ -70,6 +70,7 @@ typedef struct _Evas_GL_Polygon Evas_GL_Polygon;
typedef struct _Evas_GL_Polygon_Point Evas_GL_Polygon_Point;
typedef struct _Evas_GL_Texture_Async_Preload Evas_GL_Texture_Async_Preload;
typedef struct _Evas_GL_Image_Data_Map Evas_GL_Image_Data_Map;
typedef struct _Evas_GL_Filter Evas_GL_Filter;
typedef Eina_Bool (*evas_gl_make_current_cb)(void *engine_data, void *doit);
@ -101,16 +102,34 @@ typedef Eina_Bool (*evas_gl_make_current_cb)(void *engine_data, void *doit);
#define PROGRAM_HITCOUNT_MAX 0x1000000
struct _Evas_GL_Filter
{
struct {
GLint loc[3];
Eina_Bool known_locations;
} attribute;
struct {
GLint blur_count_loc;
GLint blur_count_value;
GLint blur_texlen_loc;
GLfloat blur_texlen_value;
GLint blur_div_loc;
GLfloat blur_div_value;
Eina_Bool known_locations;
} uniform;
struct {
GLuint tex_ids[1];
} texture;
double blur_radius;
};
struct _Evas_GL_Program
{
unsigned int flags, hitcount, tex_count;
struct {
GLuint mvp, rotation_id;
} uniform;
struct {
GLint loc_filter_data[3];
Eina_Bool known_locations;
} attribute;
Evas_GL_Filter *filter;
GLuint prog;
Eina_Bool reset : 1;
@ -652,7 +671,7 @@ void evas_gl_common_filter_displace_push(Evas_Engine_GL_Context *gc
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, double sx, double sy, double sw, double sh,
double dx, double dy, double dw, double dh, const double * const values, const double * const offsets, int count,
double dx, double dy, double dw, double dh, const double * const values, const double * const offsets, int count, double radius,
Eina_Bool horiz);
int evas_gl_common_shader_program_init(Evas_GL_Shared *shared);

View File

@ -3143,16 +3143,19 @@ _filter_data_prepare(Evas_Engine_GL_Context *gc, int pn,
Evas_GL_Program *prog, int count)
{
gc->pipe[pn].array.filter_data_count = count;
gc->pipe[pn].array.filter_data = malloc(count * 2 * sizeof(GLfloat));
if (count) gc->pipe[pn].array.filter_data = malloc(count * 2 * sizeof(GLfloat));
else gc->pipe[pn].array.filter_data = NULL;
if (!prog->attribute.known_locations)
if (!prog->filter) prog->filter = calloc(1, sizeof(*prog->filter));
if (!prog->filter->attribute.known_locations)
{
prog->filter->attribute.known_locations = EINA_TRUE;
for (int k = 0; k < count; k++)
{
char name[32];
sprintf(name, "filter_data_%d", k);
prog->attribute.loc_filter_data[k] = glGetAttribLocation(prog->prog, name);
prog->filter->attribute.loc[k] = glGetAttribLocation(prog->prog, name);
}
}
}
@ -3434,21 +3437,21 @@ evas_gl_common_filter_blur_push(Evas_Engine_GL_Context *gc,
double dx, double dy, double dw, double dh,
const double * const weights,
const double * const offsets, int count,
Eina_Bool horiz)
double radius, Eina_Bool horiz)
{
double ox1, oy1, ox2, oy2, ox3, oy3, ox4, oy4, pw, ph;
GLfloat tx1, ty1, tx2, ty2, tx3, ty3, tx4, ty4;
GLfloat offsetx, offsety;
int r, g, b, a, nomul = 0, pn;
Evas_GL_Program *prog;
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;
GLuint *map_tex_data;
GLuint map_tex;
double sum;
//shader_array_flush(gc);
r = R_VAL(&gc->dc->mul.col);
g = G_VAL(&gc->dc->mul.col);
b = B_VAL(&gc->dc->mul.col);
@ -3521,30 +3524,50 @@ evas_gl_common_filter_blur_push(Evas_Engine_GL_Context *gc,
map_tex_data[k + count] = val;
}
// Synchronous upload of Nx2 RGBA texture (FIXME: no reuse)
glGenTextures(1, &map_tex);
glBindTexture(GL_TEXTURE_2D, map_tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
if (tex->gc->shared->info.unpack_row_length)
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, sizeof(*map_tex_data));
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, count, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, map_tex_data);
// Prepare attributes & uniforms
_filter_data_prepare(gc, pn, prog, 0);
if (!prog->filter->uniform.known_locations)
{
prog->filter->uniform.known_locations = EINA_TRUE;
prog->filter->uniform.blur_count_loc = glGetUniformLocation(prog->prog, "blur_count");
prog->filter->uniform.blur_texlen_loc = glGetUniformLocation(prog->prog, "blur_texlen");
prog->filter->uniform.blur_div_loc = glGetUniformLocation(prog->prog, "blur_div");
}
// Set curve properties (no need for filter_data)
gc->pipe[pn].shader.filter.map_tex = map_tex;
// Synchronous upload of Nx2 RGBA texture
if (!EINA_DBL_EQ(prog->filter->blur_radius, radius))
{
prog->filter->blur_radius = radius;
if (!prog->filter->texture.tex_ids[0])
glGenTextures(1, prog->filter->texture.tex_ids);
glBindTexture(GL_TEXTURE_2D, prog->filter->texture.tex_ids[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
if (tex->gc->shared->info.unpack_row_length)
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, sizeof(*map_tex_data));
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, count, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, map_tex_data);
}
//if (update_uniforms)
{
prog->filter->uniform.blur_count_value = count - 1;
prog->filter->uniform.blur_texlen_value = horiz ? sw : sh;
prog->filter->uniform.blur_div_value = sum;
glUseProgram(prog->prog);
glUniform1i(prog->filter->uniform.blur_count_loc, prog->filter->uniform.blur_count_value);
glUniform1f(prog->filter->uniform.blur_texlen_loc, prog->filter->uniform.blur_texlen_value);
glUniform1f(prog->filter->uniform.blur_div_loc, prog->filter->uniform.blur_div_value);
}
// Set shader properties
gc->pipe[pn].shader.filter.map_tex = prog->filter->texture.tex_ids[0];
gc->pipe[pn].shader.filter.map_nearest = EINA_TRUE;
gc->pipe[pn].shader.filter.map_delete = EINA_TRUE;
// Set blur properties... WIP
_filter_data_prepare(gc, pn, prog, 2);
filter_data = gc->pipe[pn].array.filter_data;
filter_data[0] = count - 1.0;
filter_data[1] = horiz ? sw : sh;
filter_data[2] = sum;
filter_data[3] = 0.0; // unused
gc->pipe[pn].shader.filter.map_delete = EINA_FALSE;
pw = tex->pt->w;
ph = tex->pt->h;
@ -4241,7 +4264,7 @@ shader_array_flush(Evas_Engine_GL_Context *gc)
if (gc->pipe[i].array.filter_data)
{
for (int k = 0; k < gc->pipe[i].array.filter_data_count; k++)
glVertexAttrib2fv(prog->attribute.loc_filter_data[k], &gc->pipe[i].array.filter_data[k * 2]);
glVertexAttrib2fv(prog->filter->attribute.loc[k], &gc->pipe[i].array.filter_data[k * 2]);
}
/* Gfx filters: texture (or data array as texture) */

View File

@ -400,6 +400,12 @@ save:
static inline void
_program_del(Evas_GL_Program *p)
{
if (p->filter)
{
if (p->filter->texture.tex_ids[0])
glDeleteTextures(1, p->filter->texture.tex_ids);
free(p->filter);
}
if (p->prog) glDeleteProgram(p->prog);
free(p);
}

View File

@ -93,7 +93,9 @@ static const char fragment_glsl[] =
"#endif\n"
"#ifdef SHD_FILTER_BLUR\n"
"uniform sampler2D tex_filter;\n"
"varying vec3 blur_data;\n"
"uniform int blur_count;\n"
"uniform float blur_texlen;\n"
"uniform float blur_div;\n"
"#endif\n"
"// ----------------------------------------------------------------------------\n"
"#ifndef SHD_FILTER_BLUR\n"
@ -109,7 +111,7 @@ static const char fragment_glsl[] =
"#endif // SHD_FILTER_BLUR\n"
" vec4 c;\n"
"#ifdef SHD_FILTER_DISPLACE\n"
" vec2 dxy = texture2D(tex_filter, tex_c).rg * displace_vector;\n"
" vec2 dxy = (texture2D(tex_filter, tex_c).rg - vec2(0.5, 0.5)) * displace_vector;\n"
" float fa = texture2D(tex_filter, tex_c).a;\n"
" coord = clamp(tex_c + dxy, displace_min, displace_max);\n"
"#endif\n"
@ -234,40 +236,40 @@ static const char fragment_glsl[] =
"float weight_get(float u, float count, float index)\n"
"{\n"
" vec4 val = texture2D(tex_filter, vec2(u / count, index)).bgra;\n"
" return val.a*255.0 + (val.r*255.0/256.0) + (val.g*255.0/256.0/256.0) + (val.b*255.0/256.0/256.0/256.0);\n"
" return val.a * 255.0 + val.r + val.g / 256.0 + val.b / 65536.0;\n"
"}\n"
"float offset_get(float u, float count, float index)\n"
"{\n"
" // val.a is always 0 here ~ discard\n"
" vec4 val = texture2D(tex_filter, vec2(u / count, index)).bgra;\n"
" return (val.r*255.0/256.0) + (val.g*255.0/256.0/256.0) + (val.b*255.0/256.0/256.0/256.0);\n"
" return val.r + val.g / 256.0 + val.b / 65536.0;\n"
"}\n"
"void main()\n"
"{\n"
" float u, texlen, count, div;\n"
" float weight, offset;\n"
" float weight, offset, count;\n"
" vec4 acc, px;\n"
" count = blur_data.x;\n"
" texlen = blur_data.y;\n"
" div = blur_data.z;\n"
" int k;\n"
" count = float(blur_count);\n"
" // Center pixel, offset is 0.0\n"
" weight = weight_get(0.0, count, 0.0);\n"
" px = FETCH_PIXEL(0.0);\n"
" acc = px * weight;\n"
" for (u = 1.0; u <= count; u += 1.0)\n"
" // Left & right pixels\n"
" for (k = 1; k <= blur_count; k++)\n"
" {\n"
" float u = float(k);\n"
" weight = weight_get(u, count, 0.0);\n"
" offset = offset_get(u, count, 1.0);\n"
" // Left\n"
" vec4 px1 = FETCH_PIXEL(-((offset + (2.0 * u) - 1.0)) / texlen);\n"
" vec4 px1 = FETCH_PIXEL(-((offset + (2.0 * u) - 1.0)) / blur_texlen);\n"
" // Right\n"
" vec4 px2 = FETCH_PIXEL((offset + (2.0 * u) - 1.0) / texlen);\n"
" vec4 px2 = FETCH_PIXEL((offset + (2.0 * u) - 1.0) / blur_texlen);\n"
" acc += (px1 + px2) * weight;\n"
" }\n"
"#ifndef SHD_NOMUL\n"
" gl_FragColor = (acc / div) * col;\n"
" gl_FragColor = (acc / blur_div) * col;\n"
"#else\n"
" gl_FragColor = (acc / div);\n"
" gl_FragColor = (acc / blur_div);\n"
"#endif\n"
"}\n"
"#endif // SHD_FILTER_BLUR\n";
@ -343,12 +345,6 @@ static const char vertex_glsl[] =
"varying vec2 displace_min;\n"
"varying vec2 displace_max;\n"
"#endif\n"
"/* Gfx Filters: blur */\n"
"#ifdef SHD_FILTER_BLUR\n"
"attribute vec2 filter_data_0;\n"
"attribute vec2 filter_data_1;\n"
"varying vec3 blur_data;\n"
"#endif\n"
"void main()\n"
"{\n"
" gl_Position = mvp * vertex;\n"
@ -417,8 +413,5 @@ static const char vertex_glsl[] =
" displace_min = filter_data_1;\n"
" displace_max = filter_data_2;\n"
"#endif\n"
"#ifdef SHD_FILTER_BLUR\n"
" blur_data = vec3(filter_data_0.xy, filter_data_1.x);\n"
"#endif\n"
"}\n";

View File

@ -90,7 +90,9 @@ uniform sampler2D tex_filter;
#ifdef SHD_FILTER_BLUR
uniform sampler2D tex_filter;
varying vec3 blur_data;
uniform int blur_count;
uniform float blur_texlen;
uniform float blur_div;
#endif
// ----------------------------------------------------------------------------
@ -254,49 +256,51 @@ vec4 fetch_pixel(float ox, float oy)
float weight_get(float u, float count, float index)
{
vec4 val = texture2D(tex_filter, vec2(u / count, index)).bgra;
return val.a*255.0 + (val.r*255.0/256.0) + (val.g*255.0/256.0/256.0) + (val.b*255.0/256.0/256.0/256.0);
return val.a * 255.0 + val.r + val.g / 256.0 + val.b / 65536.0;
}
float offset_get(float u, float count, float index)
{
// val.a is always 0 here ~ discard
vec4 val = texture2D(tex_filter, vec2(u / count, index)).bgra;
return (val.r*255.0/256.0) + (val.g*255.0/256.0/256.0) + (val.b*255.0/256.0/256.0/256.0);
return val.r + val.g / 256.0 + val.b / 65536.0;
}
void main()
{
float u, texlen, count, div;
float weight, offset;
float weight, offset, count;
vec4 acc, px;
int k;
count = float(blur_count);
count = blur_data.x;
texlen = blur_data.y;
div = blur_data.z;
// Center pixel, offset is 0.0
weight = weight_get(0.0, count, 0.0);
px = FETCH_PIXEL(0.0);
acc = px * weight;
for (u = 1.0; u <= count; u += 1.0)
// Left & right pixels
for (k = 1; k <= blur_count; k++)
{
float u = float(k);
weight = weight_get(u, count, 0.0);
offset = offset_get(u, count, 1.0);
// Left
vec4 px1 = FETCH_PIXEL(-((offset + (2.0 * u) - 1.0)) / texlen);
vec4 px1 = FETCH_PIXEL(-((offset + (2.0 * u) - 1.0)) / blur_texlen);
// Right
vec4 px2 = FETCH_PIXEL((offset + (2.0 * u) - 1.0) / texlen);
vec4 px2 = FETCH_PIXEL((offset + (2.0 * u) - 1.0) / blur_texlen);
acc += (px1 + px2) * weight;
}
#ifndef SHD_NOMUL
gl_FragColor = (acc / div) * col;
gl_FragColor = (acc / blur_div) * col;
#else
gl_FragColor = (acc / div);
gl_FragColor = (acc / blur_div);
#endif
}

View File

@ -78,13 +78,6 @@ varying vec2 displace_min;
varying vec2 displace_max;
#endif
/* Gfx Filters: blur */
#ifdef SHD_FILTER_BLUR
attribute vec2 filter_data_0;
attribute vec2 filter_data_1;
varying vec3 blur_data;
#endif
void main()
{
@ -164,8 +157,4 @@ void main()
displace_min = filter_data_1;
displace_max = filter_data_2;
#endif
#ifdef SHD_FILTER_BLUR
blur_data = vec3(filter_data_0.xy, filter_data_1.x);
#endif
}

View File

@ -149,7 +149,7 @@ _gl_filter_blur(Render_Engine_GL_Generic *re, Evas_Filter_Command *cmd)
count = _gaussian_interpolate(&weights, &offsets, radius);
evas_gl_common_filter_blur_push(gc, image->tex, ssx, ssy, ssw, ssh, dx, dy, dw, dh,
weights, offsets, count, horiz);
weights, offsets, count, radius, horiz);
free(weights);
free(offsets);

View File

@ -231,7 +231,7 @@ _box_blur_apply(Evas_Filter_Command *cmd, Eina_Bool vert, Eina_Bool rgba)
int radius, regions, w, h;
void *src, *dst;
radius = abs(vert ? cmd->blur.dy : cmd->blur.dx);
radius = abs(vert ? (int) cmd->blur.dy : (int) cmd->blur.dx);
src = _buffer_map_all(cmd->input->buffer, &src_len, E_READ, rgba ? E_ARGB : E_ALPHA, &src_stride);
dst = _buffer_map_all(cmd->output->buffer, &dst_len, E_WRITE, rgba ? E_ARGB : E_ALPHA, &dst_stride);
if (!src || !dst) goto unmap;
@ -386,7 +386,7 @@ _gaussian_blur_apply(Evas_Filter_Command *cmd, Eina_Bool vert, Eina_Bool rgba)
void *src, *dst;
int *weights;
radius = abs(vert ? cmd->blur.dy : cmd->blur.dx);
radius = abs(vert ? (int) cmd->blur.dy : (int) cmd->blur.dx);
src = _buffer_map_all(cmd->input->buffer, &src_len, E_READ, rgba ? E_ARGB : E_ALPHA, &src_stride);
dst = _buffer_map_all(cmd->output->buffer, &dst_len, E_WRITE, rgba ? E_ARGB : E_ALPHA, &dst_stride);
w = cmd->input->w;