Skip to content

Commit acf6935

Browse files
committed
backend: gl_common: implement dual-filter kawase blur method
Implement the dual-filter kawase blur algorithm for the new OpenGL backend as seen in kwin [1]. Use with `--blur-method dual_kawase` and set the desired strength with `--blur-strength level` (1-20). The dual-filter kawase algorithm produces results close to a traditional gaussian blur with higher performace, especially at high blur radii. The supported strength levels provide an effect similar to gauss-radii between 4 and 500 pixels. As this algorithm relies heavily on the texture-filtering units of a GPU, there is no support for the xrender backend — at least for now. [1](https://kwin.kde.narkive.com/aSqRYYw7/d9848-updated-the-blur-method-to-use-the-more-efficient-dual-kawase-blur-algorithm)
1 parent a2e30fc commit acf6935

File tree

2 files changed

+292
-16
lines changed

2 files changed

+292
-16
lines changed

src/backend/gl/gl_common.c

+291-16
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y,
544544
* Blur contents in a particular region.
545545
*/
546546
bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent,
547+
const int width attr_unused, const int height attr_unused,
547548
const int nrects, const GLuint vao[2]) {
548549
auto bctx = (struct gl_blur_context *)ctx;
549550
auto gd = (struct gl_data *)base;
@@ -623,13 +624,119 @@ bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *ex
623624
return true;
624625
}
625626

626-
bool gl_dual_kawase_blur(backend_t *base, double opacity attr_unused, void *ctx,
627-
const rect_t *extent attr_unused, const int nrects attr_unused,
628-
const GLuint vao[2] attr_unused) {
629-
auto bctx attr_unused = (struct gl_blur_context *)ctx;
630-
auto gd attr_unused = (struct gl_data *)base;
627+
bool gl_dual_kawase_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent,
628+
const int width, const int height, const int nrects,
629+
const GLuint vao[2]) {
630+
auto bctx = (struct gl_blur_context *)ctx;
631+
auto gd = (struct gl_data *)base;
632+
633+
int dst_y_screen_coord = gd->height - extent->y2,
634+
dst_y_fb_coord = bctx->fb_height - extent->y2;
635+
636+
// Reduce number of iterations until the last one renders at least 1px in both
637+
// dimensions
638+
int iterations = bctx->blur_texture_count;
639+
while (((width / (1 << iterations)) < 1 || (height / (1 << iterations)) < 1) &&
640+
iterations > 0) {
641+
--iterations;
642+
}
643+
assert(iterations < (sizeof(int) * 8) && iterations >= 0);
644+
645+
// Kawase downsample pass
646+
const gl_blur_shader_t *down_pass = &bctx->blur_shader[0];
647+
assert(down_pass->prog);
648+
glUseProgram(down_pass->prog);
649+
650+
// Downsample always renders with resize offset
651+
glUniform2f(down_pass->orig_loc, (GLfloat)bctx->resize_width,
652+
-(GLfloat)bctx->resize_height);
653+
654+
for (int i = 0; i < iterations; ++i) {
655+
GLuint src_texture;
656+
int tex_width, tex_height;
657+
int texorig_x, texorig_y;
658+
659+
if (i == 0) {
660+
// first pass: copy from back buffer
661+
src_texture = gd->back_texture;
662+
tex_width = gd->width;
663+
tex_height = gd->height;
664+
665+
texorig_x = extent->x1;
666+
texorig_y = dst_y_screen_coord;
667+
} else {
668+
// copy from previous pass
669+
src_texture = bctx->blur_textures[i - 1];
670+
auto src_size = bctx->texture_sizes[i - 1];
671+
tex_width = src_size.width;
672+
tex_height = src_size.height;
673+
674+
texorig_x = extent->x1 + bctx->resize_width;
675+
texorig_y = dst_y_fb_coord - bctx->resize_height;
676+
}
677+
678+
assert(src_texture);
679+
assert(bctx->blur_fbos[i]);
680+
681+
glBindTexture(GL_TEXTURE_2D, src_texture);
682+
glBindVertexArray(vao[1]);
683+
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]);
684+
glDrawBuffer(GL_COLOR_ATTACHMENT0);
685+
686+
glUniform2f(down_pass->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y);
687+
glUniform1f(down_pass->scale_loc, (GLfloat)(1 << (i + 1)));
688+
689+
glUniform2f(down_pass->unifm_pixel_norm, 1.0f / (GLfloat)tex_width,
690+
1.0f / (GLfloat)tex_height);
631691

632-
// TODO: Blur with dual_kawase shaders
692+
glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL);
693+
}
694+
695+
// Kawase upsample pass
696+
const gl_blur_shader_t *up_pass = &bctx->blur_shader[1];
697+
assert(up_pass->prog);
698+
glUseProgram(up_pass->prog);
699+
700+
// Upsample always samples from textures with resize offset
701+
glUniform2f(up_pass->texorig_loc, (GLfloat)(extent->x1 + bctx->resize_width),
702+
(GLfloat)(dst_y_fb_coord - bctx->resize_height));
703+
704+
for (int i = iterations - 1; i >= 0; --i) {
705+
const GLuint src_texture = bctx->blur_textures[i];
706+
assert(src_texture);
707+
708+
// Calculate normalized half-width/-height of a src pixel
709+
auto src_size = bctx->texture_sizes[i];
710+
int tex_width = src_size.width;
711+
int tex_height = src_size.height;
712+
713+
glBindTexture(GL_TEXTURE_2D, src_texture);
714+
if (i > 0) {
715+
assert(bctx->blur_fbos[i - 1]);
716+
717+
// not last pass, draw into next framebuffer
718+
glBindVertexArray(vao[1]);
719+
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]);
720+
glDrawBuffer(GL_COLOR_ATTACHMENT0);
721+
722+
glUniform2f(up_pass->orig_loc, (GLfloat)bctx->resize_width,
723+
-(GLfloat)bctx->resize_height);
724+
glUniform1f(up_pass->unifm_opacity, (GLfloat)1);
725+
} else {
726+
// last pass, draw directly into the back buffer
727+
glBindVertexArray(vao[0]);
728+
glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo);
729+
730+
glUniform2f(up_pass->orig_loc, (GLfloat)0, (GLfloat)0);
731+
glUniform1f(up_pass->unifm_opacity, (GLfloat)opacity);
732+
}
733+
734+
glUniform1f(up_pass->scale_loc, (GLfloat)(1 << i));
735+
glUniform2f(up_pass->unifm_pixel_norm, 1.0f / (GLfloat)tex_width,
736+
1.0f / (GLfloat)tex_height);
737+
738+
glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL);
739+
}
633740

634741
return true;
635742
}
@@ -651,7 +758,10 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blu
651758
for (int i = 0; i < bctx->blur_texture_count; ++i) {
652759
auto tex_size = bctx->texture_sizes + i;
653760
if (bctx->method == BLUR_METHOD_DUAL_KAWASE) {
654-
// TODO: Use smaller textures for each iteration
761+
// Use smaller textures for each iteration (quarter of the
762+
// previous texture)
763+
tex_size->width = 1 + (bctx->fb_width - 1) / (1 << (i + 1));
764+
tex_size->height = 1 + (bctx->fb_height - 1) / (1 << (i + 1));
655765
} else {
656766
tex_size->width = bctx->fb_width;
657767
tex_size->height = bctx->fb_height;
@@ -660,7 +770,22 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blu
660770
glBindTexture(GL_TEXTURE_2D, bctx->blur_textures[i]);
661771
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_size->width,
662772
tex_size->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
773+
774+
if (bctx->method == BLUR_METHOD_DUAL_KAWASE) {
775+
// Attach texture to FBO target
776+
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]);
777+
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
778+
GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
779+
bctx->blur_textures[i], 0);
780+
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) !=
781+
GL_FRAMEBUFFER_COMPLETE) {
782+
log_error("Framebuffer attachment failed.");
783+
glBindFramebuffer(GL_FRAMEBUFFER, 0);
784+
return false;
785+
}
786+
}
663787
}
788+
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
664789
}
665790

666791
// Remainder: regions are in Xorg coordinates
@@ -725,9 +850,11 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blu
725850
sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2));
726851

727852
if (bctx->method == BLUR_METHOD_DUAL_KAWASE) {
728-
ret = gl_dual_kawase_blur(base, opacity, ctx, extent_resized, nrects, vao);
853+
ret = gl_dual_kawase_blur(base, opacity, ctx, extent_resized, width,
854+
height, nrects, vao);
729855
} else {
730-
ret = gl_kernel_blur(base, opacity, ctx, extent_resized, nrects, vao);
856+
ret = gl_kernel_blur(base, opacity, ctx, extent_resized, width, height,
857+
nrects, vao);
731858
}
732859

733860
glBindFramebuffer(GL_FRAMEBUFFER, 0);
@@ -751,13 +878,14 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blu
751878
// clang-format off
752879
const char *vertex_shader = GLSL(330,
753880
uniform mat4 projection;
881+
uniform float scale = 1.0;
754882
uniform vec2 orig;
755883
uniform vec2 texorig;
756884
layout(location = 0) in vec2 coord;
757885
layout(location = 1) in vec2 in_texcoord;
758886
out vec2 texcoord;
759887
void main() {
760-
gl_Position = projection * vec4(coord + orig, 0, 1);
888+
gl_Position = projection * vec4(coord + orig, 0, scale);
761889
texcoord = in_texcoord + texorig;
762890
}
763891
);
@@ -1190,17 +1318,164 @@ bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection,
11901318
return success;
11911319
}
11921320

1193-
bool gl_create_dual_kawase_blur_context(void *blur_context, GLfloat *projection attr_unused,
1194-
enum blur_method method attr_unused,
1195-
void *args attr_unused) {
1321+
bool gl_create_dual_kawase_blur_context(void *blur_context, GLfloat *projection,
1322+
enum blur_method method, void *args) {
11961323
bool success = false;
11971324
auto ctx = (struct gl_blur_context *)blur_context;
11981325

1199-
// TODO: Create and initialize down and upsample shader
1326+
ctx->method = method;
1327+
1328+
auto blur_params = generate_dual_kawase_params(args);
1329+
1330+
// Specify required textures and FBOs
1331+
ctx->blur_texture_count = blur_params->iterations;
1332+
ctx->blur_fbo_count = blur_params->iterations;
1333+
1334+
ctx->resize_width += blur_params->expand;
1335+
ctx->resize_height += blur_params->expand;
1336+
1337+
ctx->npasses = 2;
1338+
ctx->blur_shader = ccalloc(ctx->npasses, gl_blur_shader_t);
1339+
1340+
char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL));
1341+
// Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane
1342+
// Thanks to hiciu for reporting.
1343+
setlocale(LC_NUMERIC, "C");
1344+
1345+
// Dual-kawase downsample shader / program
1346+
auto down_pass = ctx->blur_shader;
1347+
{
1348+
// clang-format off
1349+
static const char *FRAG_SHADER_DOWN = GLSL(330,
1350+
uniform sampler2D tex_src;
1351+
uniform float scale = 1.0;
1352+
uniform vec2 pixel_norm;
1353+
in vec2 texcoord;
1354+
out vec4 out_color;
1355+
void main() {
1356+
vec2 offset = %.7g * pixel_norm;
1357+
vec2 uv = texcoord * pixel_norm * (2.0 / scale);
1358+
vec4 sum = texture2D(tex_src, uv) * 4.0;
1359+
sum += texture2D(tex_src, uv - vec2(0.5, 0.5) * offset);
1360+
sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset);
1361+
sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset);
1362+
sum += texture2D(tex_src, uv - vec2(0.5, -0.5) * offset);
1363+
out_color = sum / 8.0;
1364+
}
1365+
);
1366+
// clang-format on
1367+
1368+
// Build shader
1369+
size_t shader_len =
1370+
strlen(FRAG_SHADER_DOWN) + 10 /* offset */ + 1 /* null terminator */;
1371+
char *shader_str = ccalloc(shader_len, char);
1372+
auto real_shader_len =
1373+
snprintf(shader_str, shader_len, FRAG_SHADER_DOWN, blur_params->offset);
1374+
CHECK(real_shader_len >= 0);
1375+
CHECK((size_t)real_shader_len < shader_len);
1376+
1377+
// Build program
1378+
down_pass->prog = gl_create_program_from_str(vertex_shader, shader_str);
1379+
free(shader_str);
1380+
if (!down_pass->prog) {
1381+
log_error("Failed to create GLSL program.");
1382+
success = false;
1383+
goto out;
1384+
}
1385+
glBindFragDataLocation(down_pass->prog, 0, "out_color");
1386+
1387+
// Get uniform addresses
1388+
down_pass->unifm_pixel_norm =
1389+
glGetUniformLocationChecked(down_pass->prog, "pixel_norm");
1390+
down_pass->orig_loc =
1391+
glGetUniformLocationChecked(down_pass->prog, "orig");
1392+
down_pass->texorig_loc =
1393+
glGetUniformLocationChecked(down_pass->prog, "texorig");
1394+
down_pass->scale_loc =
1395+
glGetUniformLocationChecked(down_pass->prog, "scale");
1396+
1397+
// Setup projection matrix
1398+
glUseProgram(down_pass->prog);
1399+
int pml = glGetUniformLocationChecked(down_pass->prog, "projection");
1400+
glUniformMatrix4fv(pml, 1, false, projection);
1401+
glUseProgram(0);
1402+
}
1403+
1404+
// Dual-kawase upsample shader / program
1405+
auto up_pass = ctx->blur_shader + 1;
1406+
{
1407+
// clang-format off
1408+
static const char *FRAG_SHADER_UP = GLSL(330,
1409+
uniform sampler2D tex_src;
1410+
uniform float scale = 1.0;
1411+
uniform vec2 pixel_norm;
1412+
uniform float opacity;
1413+
in vec2 texcoord;
1414+
out vec4 out_color;
1415+
void main() {
1416+
vec2 offset = %.7g * pixel_norm;
1417+
vec2 uv = texcoord * pixel_norm / (2 * scale);
1418+
vec4 sum = texture2D(tex_src, uv + vec2(-1.0, 0.0) * offset);
1419+
sum += texture2D(tex_src, uv + vec2(-0.5, 0.5) * offset) * 2.0;
1420+
sum += texture2D(tex_src, uv + vec2(0.0, 1.0) * offset);
1421+
sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset) * 2.0;
1422+
sum += texture2D(tex_src, uv + vec2(1.0, 0.0) * offset);
1423+
sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset) * 2.0;
1424+
sum += texture2D(tex_src, uv + vec2(0.0, -1.0) * offset);
1425+
sum += texture2D(tex_src, uv + vec2(-0.5, -0.5) * offset) * 2.0;
1426+
out_color = sum / 12.0 * opacity;
1427+
}
1428+
);
1429+
// clang-format on
1430+
1431+
// Build shader
1432+
size_t shader_len =
1433+
strlen(FRAG_SHADER_UP) + 10 /* offset */ + 1 /* null terminator */;
1434+
char *shader_str = ccalloc(shader_len, char);
1435+
auto real_shader_len =
1436+
snprintf(shader_str, shader_len, FRAG_SHADER_UP, blur_params->offset);
1437+
CHECK(real_shader_len >= 0);
1438+
CHECK((size_t)real_shader_len < shader_len);
1439+
1440+
// Build program
1441+
up_pass->prog = gl_create_program_from_str(vertex_shader, shader_str);
1442+
free(shader_str);
1443+
if (!up_pass->prog) {
1444+
log_error("Failed to create GLSL program.");
1445+
success = false;
1446+
goto out;
1447+
}
1448+
glBindFragDataLocation(up_pass->prog, 0, "out_color");
1449+
1450+
// Get uniform addresses
1451+
up_pass->unifm_pixel_norm =
1452+
glGetUniformLocationChecked(up_pass->prog, "pixel_norm");
1453+
up_pass->unifm_opacity =
1454+
glGetUniformLocationChecked(up_pass->prog, "opacity");
1455+
up_pass->orig_loc = glGetUniformLocationChecked(up_pass->prog, "orig");
1456+
up_pass->texorig_loc =
1457+
glGetUniformLocationChecked(up_pass->prog, "texorig");
1458+
up_pass->scale_loc = glGetUniformLocationChecked(up_pass->prog, "scale");
1459+
1460+
// Setup projection matrix
1461+
glUseProgram(up_pass->prog);
1462+
int pml = glGetUniformLocationChecked(up_pass->prog, "projection");
1463+
glUniformMatrix4fv(pml, 1, false, projection);
1464+
glUseProgram(0);
1465+
}
12001466

1201-
log_warn("Blur method 'dual_kawase' is not yet implemented.");
1202-
ctx->method = BLUR_METHOD_NONE;
12031467
success = true;
1468+
out:
1469+
free(blur_params);
1470+
1471+
if (!success) {
1472+
ctx = NULL;
1473+
}
1474+
1475+
// Restore LC_NUMERIC
1476+
setlocale(LC_NUMERIC, lc_numeric_old);
1477+
free(lc_numeric_old);
1478+
12041479
return success;
12051480
}
12061481

src/backend/gl/gl_common.h

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ typedef struct {
3636
GLint unifm_opacity;
3737
GLint orig_loc;
3838
GLint texorig_loc;
39+
GLint scale_loc;
3940
} gl_blur_shader_t;
4041

4142
typedef struct {

0 commit comments

Comments
 (0)