Skip to content

Commit dbe9e1f

Browse files
committed
perf: blur-res readback
1 parent 51f5f2a commit dbe9e1f

3 files changed

Lines changed: 160 additions & 44 deletions

File tree

include/effects.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,12 @@ typedef struct effects_output_t {
5858
// shared blurred background, reused by every window blur
5959
struct wlr_buffer *blur_buf;
6060
uint64_t blur_native[2];
61+
int blur_buf_w, blur_buf_h;
6162

6263
// shared blurred layer background, reused by every layer surface blur
6364
struct wlr_buffer *layer_blur_buf;
6465
uint64_t layer_blur_native[2];
66+
int layer_blur_buf_w, layer_blur_buf_h;
6567

6668
struct wlr_buffer *screen_shader_buf;
6769
uint64_t screen_shader_native[2];

src/effects.c

Lines changed: 144 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -515,9 +515,16 @@ static uint64_t capture_bg_to_tex1_ex(output_t *output, effects_output_t *ctx, b
515515
}
516516
}
517517

518+
// render the scene directly into a blur-resolution buffer by scaling the capture output down
519+
struct wlr_output_state cap_state;
520+
wlr_output_state_init(&cap_state);
521+
wlr_output_state_set_enabled(&cap_state, true);
522+
wlr_output_state_set_custom_mode(&cap_state, ctx->blur_w, ctx->blur_h, 0);
523+
wlr_output_state_set_scale(&cap_state, (float)ctx->blur_w / (float)w);
524+
518525
wlr_damage_ring_add_whole(&ctx->capture_scene_output->damage_ring);
519526

520-
bool ok = wlr_scene_output_build_state(ctx->capture_scene_output, &ctx->capture_state, NULL);
527+
bool ok = wlr_scene_output_build_state(ctx->capture_scene_output, &cap_state, NULL);
521528

522529
if (hide_node) {
523530
if (*hide_flag)
@@ -558,18 +565,16 @@ static uint64_t capture_bg_to_tex1_ex(output_t *output, effects_output_t *ctx, b
558565

559566
wlr_scene_output_set_position(ctx->capture_scene_output, -0x7fff, -0x7fff);
560567

561-
if (!ok || !ctx->capture_state.buffer) {
568+
if (!ok || !cap_state.buffer) {
562569
wlr_log(WLR_INFO, "capture_bg_to_tex1: no buffer from build_state");
563-
wlr_buffer_unlock(ctx->capture_state.buffer);
564-
ctx->capture_state.buffer = NULL;
570+
wlr_output_state_finish(&cap_state);
565571
return 0;
566572
}
567573

568574
uint64_t result;
569-
effects_backend->capture_readback(ctx->capture_state.buffer, &ctx->be_state,
570-
ctx->be_state.pong.native_handle[0], ctx->blur_w, ctx->blur_h, w, h, &result);
571-
wlr_buffer_unlock(ctx->capture_state.buffer);
572-
ctx->capture_state.buffer = NULL;
575+
effects_backend->capture_readback(cap_state.buffer, &ctx->be_state,
576+
ctx->be_state.pong.native_handle[0], ctx->blur_w, ctx->blur_h, ctx->blur_w, ctx->blur_h, &result);
577+
wlr_output_state_finish(&cap_state);
573578

574579
return result;
575580
}
@@ -659,7 +664,9 @@ static bool rebuild_live_blur(output_t *output, uint64_t shared_blurred, pixman_
659664
}
660665

661666
// blur the shared background once and reuse it for every window
662-
if (!ensure_output_buf(&ctx->blur_buf, ctx->blur_native, w, h)) {
667+
// buffer stays at blur resolution, scene upscales it when drawing
668+
if (!ensure_sized_buf(&ctx->blur_buf, ctx->blur_native, &ctx->blur_buf_w, &ctx->blur_buf_h,
669+
ctx->blur_w, ctx->blur_h)) {
663670
toplevel_t *tl;
664671
wl_list_for_each(tl, &server.toplevels, link) {
665672
if (!tl->blur || !tl->blur->blur_node || !tl->node || !tl->node->client)
@@ -676,7 +683,7 @@ static bool rebuild_live_blur(output_t *output, uint64_t shared_blurred, pixman_
676683

677684
uint64_t blurred;
678685
effects_backend->blur(&ctx->be_state, shared_blurred, ctx->blur_w, ctx->blur_h, &bp, &blurred);
679-
effects_backend->blit(blurred, ctx->blur_native[0], w, h, NULL, 0);
686+
effects_backend->blit(blurred, ctx->blur_native[0], ctx->blur_w, ctx->blur_h, NULL, 0);
680687
any = true;
681688

682689
// only windows with compositor-rounded corners need a per-window buffer
@@ -761,6 +768,12 @@ static void push_blur_to_toplevels(output_t *output) {
761768
if (tl->blur->blur_node->buffer != buf)
762769
wlr_scene_buffer_set_buffer(tl->blur->blur_node, buf);
763770

771+
// source box lives in the buffer's coordinate space
772+
// shared buffer is at blur resolution (upscaled by the scene)
773+
// masked windows use their own full-resolution buffer, so their source box is unchanged
774+
float src_scale_x = masked ? 1.0f : (float)ctx->blur_w / (float)output->width;
775+
float src_scale_y = masked ? 1.0f : (float)ctx->blur_h / (float)output->height;
776+
764777
if (tl->blur && !pixman_region32_empty(&tl->blur->blur_region)) {
765778
int lx = 0, ly = 0;
766779
wlr_scene_node_coords(&tl->scene_tree->node, &lx, &ly);
@@ -789,6 +802,10 @@ static void push_blur_to_toplevels(output_t *output) {
789802
int offset_y = (blur_rect.y < output->ly) ? (output->ly - blur_rect.y) : 0;
790803
wlr_scene_node_set_position(&tl->blur->blur_node->node, blur_r_x + offset_x,
791804
blur_r_y + offset_y);
805+
src.x *= src_scale_x;
806+
src.y *= src_scale_y;
807+
src.width *= src_scale_x;
808+
src.height *= src_scale_y;
792809
wlr_scene_buffer_set_source_box(tl->blur->blur_node, &src);
793810
wlr_scene_buffer_set_dest_size(tl->blur->blur_node, dw, dh);
794811
} else {
@@ -805,6 +822,10 @@ static void push_blur_to_toplevels(output_t *output) {
805822
int node_ox = (r.x < output->lx) ? (output->lx - r.x) : 0;
806823
int node_oy = (r.y < output->ly) ? (output->ly - r.y) : 0;
807824
wlr_scene_node_set_position(&tl->blur->blur_node->node, node_ox, node_oy);
825+
src.x *= src_scale_x;
826+
src.y *= src_scale_y;
827+
src.width *= src_scale_x;
828+
src.height *= src_scale_y;
808829
wlr_scene_buffer_set_source_box(tl->blur->blur_node, &src);
809830
wlr_scene_buffer_set_dest_size(tl->blur->blur_node, dw, dh);
810831
}
@@ -814,7 +835,6 @@ static void push_blur_to_toplevels(output_t *output) {
814835
static bool rebuild_live_blur_layers(output_t *output, uint64_t bg_tex, pixman_region32_t *damage) {
815836
(void)damage;
816837
effects_output_t *ctx = output->effects;
817-
int w = output->width, h = output->height;
818838

819839
struct be_blur_params bp = {
820840
.algorithm = blur_algorithm,
@@ -828,18 +848,18 @@ static bool rebuild_live_blur_layers(output_t *output, uint64_t bg_tex, pixman_r
828848
};
829849

830850
// capture the background with blur toplevels visible
831-
832851
if (!bg_tex)
833852
bg_tex = capture_bg_combined(output, ctx);
834853
if (!bg_tex)
835854
return false;
836855

837-
if (!ensure_output_buf(&ctx->layer_blur_buf, ctx->layer_blur_native, w, h))
856+
if (!ensure_sized_buf(&ctx->layer_blur_buf, ctx->layer_blur_native, &ctx->layer_blur_buf_w,
857+
&ctx->layer_blur_buf_h, ctx->blur_w, ctx->blur_h))
838858
return false;
839859

840860
uint64_t blurred;
841861
effects_backend->blur(&ctx->be_state, bg_tex, ctx->blur_w, ctx->blur_h, &bp, &blurred);
842-
effects_backend->blit(blurred, ctx->layer_blur_native[0], w, h, NULL, 0);
862+
effects_backend->blit(blurred, ctx->layer_blur_native[0], ctx->blur_w, ctx->blur_h, NULL, 0);
843863
return true;
844864
}
845865

@@ -873,6 +893,8 @@ static bool layer_blur_needs_rebuild(output_t *output, pixman_region32_t *damage
873893
static void push_blur_to_layers(output_t *output) {
874894
effects_output_t *ctx = output->effects;
875895
struct wlr_buffer *buf = ctx->layer_blur_buf;
896+
float src_scale_x = (float)ctx->blur_w / (float)output->width;
897+
float src_scale_y = (float)ctx->blur_h / (float)output->height;
876898
for (int i = 0; i < 4; i++) {
877899
layer_surface_t *ls;
878900
wl_list_for_each(ls, &output->layers[i], link) {
@@ -932,6 +954,10 @@ static void push_blur_to_layers(output_t *output) {
932954

933955
// position at blur region offset within surface
934956
wlr_scene_node_set_position(&ls->blur_node->node, blur_r_x + offset_x, blur_r_y + offset_y);
957+
src.x *= src_scale_x;
958+
src.y *= src_scale_y;
959+
src.width *= src_scale_x;
960+
src.height *= src_scale_y;
935961
wlr_scene_buffer_set_source_box(ls->blur_node, &src);
936962
wlr_scene_buffer_set_dest_size(ls->blur_node, dw, dh);
937963
}
@@ -1669,38 +1695,113 @@ void effects_evict_buffers(void) {
16691695
}
16701696
}
16711697

1672-
static bool damage_in_tree(pixman_region32_t *damage, struct wlr_scene_tree *tree) {
1673-
int nrects;
1674-
pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects);
1675-
for (int i = 0; i < nrects; i++) {
1676-
double cx = (rects[i].x1 + rects[i].x2) * 0.5;
1677-
double cy = (rects[i].y1 + rects[i].y2) * 0.5;
1678-
double nx, ny;
1679-
if (wlr_scene_node_at(&tree->node, cx, cy, &nx, &ny))
1680-
return true;
1681-
}
1682-
return false;
1683-
}
1684-
1685-
static bool background_capture_needed(struct wlr_scene_output *scene_output) {
1698+
// returns true if the current damage overlaps any active effect region on this output
1699+
static bool effect_regions_damaged(output_t *output, struct wlr_scene_output *scene_output) {
16861700
pixman_region32_t *damage = &scene_output->damage_ring.current;
16871701
if (pixman_region32_empty(damage))
16881702
return false;
16891703

1690-
// only check trees not hidden during background capture
1691-
struct wlr_scene_tree *relevant[] = {
1692-
server.bg_tree,
1693-
server.bot_tree,
1694-
server.tile_tree,
1695-
server.float_tree,
1696-
server.shader_tree,
1697-
server.drag_tree,
1698-
};
1704+
effects_output_t *ctx = output->effects;
1705+
pixman_region32_t *region = &ctx->scratch_region_a;
1706+
pixman_region32_clear(region);
16991707

1700-
for (size_t i = 0; i < sizeof(relevant) / sizeof(relevant[0]); i++)
1701-
if (damage_in_tree(damage, relevant[i]))
1702-
return true;
1708+
toplevel_t *tl;
1709+
wl_list_for_each(tl, &server.toplevels, link) {
1710+
if (!tl->node || !tl->node->client || !tl->node->client->flags.shown)
1711+
continue;
1712+
if (!tl->node->output || tl->node->output != output)
1713+
continue;
1714+
if (!tl->blur || (!tl->blur->blur_node && !tl->blur->acrylic_node && !tl->blur->mica_node))
1715+
continue;
17031716

1717+
struct wlr_box r;
1718+
if (tl->blur->blur_node && !pixman_region32_empty(&tl->blur->blur_region)) {
1719+
int lx, ly;
1720+
if (!wlr_scene_node_coords(&tl->scene_tree->node, &lx, &ly))
1721+
continue;
1722+
r = (struct wlr_box){
1723+
.x = lx - output->lx + tl->blur->blur_region.extents.x1,
1724+
.y = ly - output->ly + tl->blur->blur_region.extents.y1,
1725+
.width = tl->blur->blur_region.extents.x2 - tl->blur->blur_region.extents.x1,
1726+
.height = tl->blur->blur_region.extents.y2 - tl->blur->blur_region.extents.y1,
1727+
};
1728+
} else {
1729+
r = get_animated_client_rect(tl);
1730+
r.x -= output->lx;
1731+
r.y -= output->ly;
1732+
}
1733+
if (r.width <= 0 || r.height <= 0)
1734+
continue;
1735+
pixman_region32_union_rect(region, region, r.x, r.y, r.width, r.height);
1736+
}
1737+
1738+
for (int i = 0; i < 4; i++) {
1739+
layer_surface_t *ls;
1740+
wl_list_for_each(ls, &output->layers[i], link) {
1741+
if (!ls->blur_node || !ls->mapped)
1742+
continue;
1743+
if (pixman_region32_empty(&ls->blur_region))
1744+
continue;
1745+
int lx, ly;
1746+
if (!wlr_scene_node_coords(&ls->scene_tree->node, &lx, &ly))
1747+
continue;
1748+
struct wlr_box r = {
1749+
.x = lx - output->lx + ls->blur_region.extents.x1,
1750+
.y = ly - output->ly + ls->blur_region.extents.y1,
1751+
.width = ls->blur_region.extents.x2 - ls->blur_region.extents.x1,
1752+
.height = ls->blur_region.extents.y2 - ls->blur_region.extents.y1,
1753+
};
1754+
pixman_region32_union_rect(region, region, r.x, r.y, r.width, r.height);
1755+
}
1756+
}
1757+
1758+
// corner-mask windows show the sharp backdrop behind their corners
1759+
wl_list_for_each(tl, &server.toplevels, link) {
1760+
if (!tl->node || !tl->node->client || !tl->node->client->flags.shown)
1761+
continue;
1762+
if (!tl->node->output || tl->node->output != output)
1763+
continue;
1764+
if (!tl->rounded || !tl->rounded->corner_mask_node)
1765+
continue;
1766+
client_t *c = tl->node->client;
1767+
if (c->border_radius <= 0.0f || c->state == STATE_FULLSCREEN)
1768+
continue;
1769+
struct wlr_box r = get_animated_client_rect(tl);
1770+
r.x -= output->lx;
1771+
r.y -= output->ly;
1772+
pixman_region32_union_rect(region, region, r.x, r.y, r.width, r.height);
1773+
}
1774+
1775+
if (pixman_region32_empty(region))
1776+
return false;
1777+
1778+
pixman_region32_intersect(&ctx->scratch_region_b, damage, region);
1779+
return !pixman_region32_empty(&ctx->scratch_region_b);
1780+
}
1781+
1782+
// returns true if a window/layer has a pending change that must be pushed to the
1783+
// scene even when no backdrop damage intersects an effect region
1784+
static bool effects_pending_update(output_t *output) {
1785+
toplevel_t *tl;
1786+
wl_list_for_each(tl, &server.toplevels, link) {
1787+
if (!tl->node || !tl->node->client || !tl->node->client->flags.shown)
1788+
continue;
1789+
if (!tl->node->output || tl->node->output != output)
1790+
continue;
1791+
if (tl->blur && tl->blur->blur_node && tl->blur->blur_region_dirty)
1792+
return true;
1793+
if (tl->shadow && tl->node->client->flags.shadow && (tl->shadow->shadow_dirty ||
1794+
tl->shadow->shadow_geometry_dirty))
1795+
return true;
1796+
if (tl->rounded && tl->rounded->corner_mask_dirty)
1797+
return true;
1798+
}
1799+
for (int i = 0; i < 4; i++) {
1800+
layer_surface_t *ls;
1801+
wl_list_for_each(ls, &output->layers[i], link)
1802+
if (ls->blur_node && ls->mapped && ls->blur_region_dirty)
1803+
return true;
1804+
}
17041805
return false;
17051806
}
17061807

@@ -1779,10 +1880,11 @@ void effects_output_frame(output_t *output, struct wlr_scene_output *scene_outpu
17791880

17801881
effects_backend->frame_begin();
17811882

1782-
bool bg_damaged = workspace_warmup || background_capture_needed(scene_output);
1883+
bool bg_damaged = workspace_warmup || effect_regions_damaged(output,
1884+
scene_output) || effects_pending_update(output);
17831885
bool mica_dirty = mica_enabled && ctx->mica_dirty;
17841886

1785-
// when no relevant damage and mica not dirty, skip all effects work
1887+
// when no effect region is damaged and no effect state is pending, skip all effects work
17861888
if (!bg_damaged && !mica_dirty)
17871889
goto after_capture;
17881890

src/effects_gles2.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,10 +1007,22 @@ static bool gles2_capture_readback(struct wlr_buffer *capture_buffer, be_output_
10071007
if (dst_fbo == state->screen_shader.native_handle[0])
10081008
result_tex = (GLuint)state->screen_shader.native_handle[1];
10091009
} else if (attach_type == GL_RENDERBUFFER) {
1010-
GLuint staging_tex = (GLuint)state->staging.native_handle[1];
1011-
10121010
// ensure renderbuffer rendering is complete before copy
10131011
glFinish();
1012+
1013+
// resize staging texture to match so blit below is a 1:1 copy
1014+
if (state->staging.width != src_w || state->staging.height != src_h) {
1015+
destroy_fbo((GLuint *)&state->staging.native_handle[0],
1016+
(GLuint *)&state->staging.native_handle[1]);
1017+
if (!create_fbo(src_w, src_h, (GLuint *)&state->staging.native_handle[0],
1018+
(GLuint *)&state->staging.native_handle[1]))
1019+
return false;
1020+
state->staging.width = src_w;
1021+
state->staging.height = src_h;
1022+
}
1023+
1024+
GLuint staging_tex = (GLuint)state->staging.native_handle[1];
1025+
10141026
glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo);
10151027
glBindTexture(GL_TEXTURE_2D, staging_tex);
10161028
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, src_w, src_h);

0 commit comments

Comments
 (0)