1
0
Fork 0
mirror of https://github.com/yshui/picom.git synced 2025-04-21 18:03:02 -04:00

renderer: implement damage calculation

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2024-04-17 08:49:10 +01:00
parent c1f996f795
commit 24aa1e68d8
No known key found for this signature in database
GPG key ID: D3A4405BE6CC17F4
8 changed files with 470 additions and 10 deletions

View file

@ -11,7 +11,7 @@ srcs = [ files('picom.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', 'utils.
'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c', 'log.c',
'options.c', 'event.c', 'cache.c', 'atom.c', 'file_watch.c', 'statistics.c',
'vblank.c', 'transition.c', 'wm.c', 'renderer/layout.c', 'renderer/command_builder.c',
'renderer/renderer.c') ]
'renderer/renderer.c', 'renderer/damage.c') ]
picom_inc = include_directories('.')
cflags = []

View file

@ -147,3 +147,28 @@ static inline void region_union(region_t *region, struct coord origin, const reg
pixman_region32_union(region, region, other);
pixman_region32_translate(region, origin.x, origin.y);
}
/// Intersect `region` with `other` placed at `origin`.
static inline void
region_intersect(region_t *region, struct coord origin, const region_t *other) {
pixman_region32_translate(region, -origin.x, -origin.y);
pixman_region32_intersect(region, region, other);
pixman_region32_translate(region, origin.x, origin.y);
}
/// Calculate the symmetric difference of `region1`, and `region2` placed at
/// `origin2`, and union the result into `result`.
///
/// @param scratch a region to store temporary results
static inline void
region_symmetric_difference(region_t *result, region_t *scratch, struct coord origin1,
const region_t *region1, struct coord origin2,
const region_t *region2) {
pixman_region32_copy(scratch, region1);
region_subtract(scratch, coord_sub(origin2, origin1), region2);
region_union(result, origin1, scratch);
pixman_region32_copy(scratch, region2);
region_subtract(scratch, coord_sub(origin1, origin2), region1);
region_union(result, origin2, scratch);
}

295
src/renderer/damage.c Normal file
View file

@ -0,0 +1,295 @@
#include "damage.h"
#include "layout.h"
#include "region.h"
#include "win.h"
static inline bool attr_unused layer_key_eq(const struct layer_key *a,
const struct layer_key *b) {
if (!a->generation || !b->generation) {
return false;
}
return a->window == b->window && a->generation == b->generation;
}
/// Compare two layers that contain the same window, return if they are the "same". Same
/// means these two layers are render in the same way at the same position, with the only
/// possible differences being the contents inside the window.
static bool
layer_compare(const struct layer *past_layer, const struct backend_command *past_layer_cmd,
const struct layer *curr_layer, const struct backend_command *curr_layer_cmd) {
if (past_layer->origin.x != curr_layer->origin.x ||
past_layer->origin.y != curr_layer->origin.y ||
past_layer->size.width != curr_layer->size.width ||
past_layer->size.height != curr_layer->size.height) {
// Window moved or size changed
return false;
}
if (past_layer->shadow_origin.x != curr_layer->shadow_origin.x ||
past_layer->shadow_origin.y != curr_layer->shadow_origin.y ||
past_layer->shadow_size.width != curr_layer->shadow_size.width ||
past_layer->shadow_size.height != curr_layer->shadow_size.height) {
// Shadow moved or size changed
return false;
}
if (past_layer->number_of_commands != curr_layer->number_of_commands) {
// Number of render commands changed. We are being conservative
// here, because even though the number of commands changed, we can still
// try to match them up. For example, maybe this window just has shadow
// disabled, but other commands are still the same. We are not do that
// here, this could be a TODO
// TODO(yshui) match render commands here
return false;
}
for (unsigned i = 0; i < past_layer->number_of_commands; i++) {
auto cmd1 = &past_layer_cmd[i];
auto cmd2 = &curr_layer_cmd[i];
if (cmd1->op != cmd2->op || !coord_eq(cmd1->origin, cmd2->origin) ||
cmd1->source != cmd2->source) {
return false;
}
}
return true;
}
/// Add all regions of `layer`'s commands to `region`
static inline void region_union_render_layer(region_t *region, const struct layer *layer,
const struct backend_command *cmds) {
for (auto i = cmds; i < &cmds[layer->number_of_commands]; i++) {
region_union(region, coord_add(i->origin, i->mask.origin), &i->mask.region);
}
}
static inline void
command_blit_damage(region_t *damage, region_t *scratch_region, struct backend_command *cmd1,
struct backend_command *cmd2, const struct layout_manager *lm,
unsigned layer_index, unsigned buffer_age) {
auto origin1 = coord_add(cmd1->origin, cmd1->mask.origin);
auto origin2 = coord_add(cmd2->origin, cmd2->mask.origin);
// clang-format off
// First part, if any blit argument that would affect the whole image changed
if (cmd1->blit.dim != cmd2->blit.dim ||
cmd1->blit.shader != cmd2->blit.shader ||
cmd1->blit.opacity != cmd2->blit.opacity ||
cmd1->blit.corner_radius != cmd2->blit.corner_radius ||
cmd1->blit.max_brightness != cmd2->blit.max_brightness ||
cmd1->blit.color_inverted != cmd2->blit.color_inverted ||
// Second part, if round corner is enabled, then border width and effective size
// affect the whole image too.
(cmd1->blit.corner_radius > 0 &&
(cmd1->blit.border_width != cmd2->blit.border_width ||
cmd1->blit.ewidth != cmd2->blit.ewidth ||
cmd1->blit.eheight != cmd2->blit.eheight )))
{
region_union(damage, origin1, &cmd1->mask.region);
region_union(damage, origin2, &cmd2->mask.region);
return;
}
// clang-format on
if (cmd1->blit.opacity == 0) {
return;
}
// Damage from layers below that is covered up by the current layer, won't be
// visible. So remove them.
pixman_region32_subtract(damage, damage, &cmd2->opaque_region);
region_symmetric_difference(damage, scratch_region, origin1, &cmd1->mask.region,
origin2, &cmd2->mask.region);
if (cmd1->source == BACKEND_COMMAND_SOURCE_WINDOW) {
layout_manager_collect_window_damage(lm, layer_index, buffer_age,
scratch_region);
region_intersect(scratch_region, origin1, &cmd1->mask.region);
region_intersect(scratch_region, origin2, &cmd2->mask.region);
pixman_region32_union(damage, damage, scratch_region);
}
}
static inline void
command_blur_damage(region_t *damage, region_t *scratch_region, struct backend_command *cmd1,
struct backend_command *cmd2, struct geometry blur_size) {
auto origin1 = coord_add(cmd1->origin, cmd1->mask.origin);
auto origin2 = coord_add(cmd2->origin, cmd2->mask.origin);
if (cmd1->blur.opacity != cmd2->blur.opacity) {
region_union(damage, origin1, &cmd1->mask.region);
region_union(damage, origin2, &cmd2->mask.region);
return;
}
if (cmd1->blur.opacity == 0) {
return;
}
region_symmetric_difference(damage, scratch_region, origin1, &cmd1->mask.region,
origin2, &cmd2->mask.region);
// We need to expand the damage region underneath the blur. Because blur
// "diffuses" the changes from below.
pixman_region32_copy(scratch_region, damage);
resize_region_in_place(scratch_region, blur_size.width, blur_size.height);
region_intersect(scratch_region, origin2, &cmd2->mask.region);
pixman_region32_union(damage, damage, scratch_region);
}
/// Do the first step of render planning, collecting damages and calculating which
/// parts of the final screen will be affected by the damages.
void layout_manager_damage(struct layout_manager *lm, unsigned buffer_age,
struct geometry blur_size, region_t *damage) {
log_trace("Damage for buffer age %d", buffer_age);
unsigned past_layer_rank = 0, curr_layer_rank = 0;
auto past_layout = layout_manager_layout(lm, buffer_age);
auto curr_layout = layout_manager_layout(lm, 0);
auto past_layer = &past_layout->layers[past_layer_rank];
auto curr_layer = &curr_layout->layers[curr_layer_rank];
auto past_layer_cmd = &past_layout->commands[past_layout->first_layer_start];
auto curr_layer_cmd = &curr_layout->commands[curr_layout->first_layer_start];
region_t scratch_region;
pixman_region32_init(&scratch_region);
pixman_region32_clear(damage);
if (past_layout->size.width != curr_layout->size.width ||
past_layout->size.height != curr_layout->size.height) {
// We might be able to do better for blur size change, but currently we
// don't even support changing that.
pixman_region32_union_rect(damage, damage, 0, 0,
(unsigned)curr_layout->size.width,
(unsigned)curr_layout->size.height);
return;
}
if (log_get_level_tls() <= LOG_LEVEL_TRACE) {
log_trace("Comparing across %d layouts:", buffer_age);
for (unsigned l = 0; l <= buffer_age; l++) {
log_trace("Layout[%d]: ", -l);
auto layout = layout_manager_layout(lm, l);
for (unsigned i = 0; i < layout->len; i++) {
log_trace(
"\t%#010x %dx%d+%dx%d (prev %d, next %d)",
layout->layers[i].key.window, layout->layers[i].size.width,
layout->layers[i].size.height,
layout->layers[i].origin.x, layout->layers[i].origin.y,
layout->layers[i].prev_rank, layout->layers[i].next_rank);
}
}
}
// Explanation of what's happening here. We want to get damage by comparing
// `past_layout` and `curr_layout` But windows in them could be different. And
// comparing different windows doesn't really make sense. So we want to "align"
// the layouts so we compare matching windows and skip over non-matching ones. For
// example, say past layout has window "ABCDE"; and in current layout, window C is
// closed, and F is opened: "ABDFE", we want to align them like this:
// ABCD E
// AB DFE
// Note there can be multiple ways of aligning windows, some of them are not
// optimal. For example, in layout "ABCDEFG", if we move B to after F: "ACDEFBG",
// we want to align them like this:
// ABCDEF G
// A CDEFBG
// not like this:
// A BCDEFG
// ACDEFB G
//
// This is the classic Longest Common Sequence (LCS) problem, but we are not doing
// a full LCS algorithm here. Since damage is calculated every frame, there is
// likely not a lot of changes between the two layouts. We use a simple linear
// time greedy approximation that should work well enough in those cases.
for (;; past_layer_rank += 1, curr_layer_rank += 1,
past_layer_cmd += past_layer->number_of_commands,
curr_layer_cmd += curr_layer->number_of_commands, past_layer += 1,
curr_layer += 1) {
int past_layer_curr_rank = -1, curr_layer_past_rank = -1;
unsigned past_layer_rank_target = past_layer_rank,
curr_layer_rank_target = curr_layer_rank;
log_region(TRACE, damage);
// Skip layers in the past layout doesn't contain a window that has a
// match in the remaining layers of the current layout; and vice versa.
while (past_layer_rank_target < past_layout->len) {
past_layer_curr_rank =
layer_next_rank(lm, buffer_age, past_layer_rank_target);
if (past_layer_curr_rank >= (int)curr_layer_rank) {
break;
}
past_layer_rank_target++;
};
while (curr_layer_rank_target < curr_layout->len) {
curr_layer_past_rank =
layer_prev_rank(lm, buffer_age, curr_layer_rank_target);
if (curr_layer_past_rank >= (int)past_layer_rank) {
break;
}
curr_layer_rank_target++;
};
// past_layer_curr_rank/curr_layer_past_rank can be -1
if (past_layer_curr_rank >= (int)curr_layer_rank ||
curr_layer_past_rank >= (int)past_layer_rank) {
// Now past_layer_current_rank and current_layer_past_rank both
// have a matching layer in the other layout. We check which side
// has less layers to skip.
assert((unsigned)curr_layer_past_rank >= past_layer_rank_target);
assert((unsigned)past_layer_curr_rank >= curr_layer_rank_target);
// How many layers will be skipped on either side if we move
// past_layer_rank to past_layer_rank_target. And vice versa.
auto skipped_using_past_target =
past_layer_rank_target - past_layer_rank +
((unsigned)past_layer_curr_rank - curr_layer_rank);
auto skipped_using_curr_target =
curr_layer_rank_target - curr_layer_rank +
((unsigned)curr_layer_past_rank - past_layer_rank);
if (skipped_using_curr_target < skipped_using_past_target) {
past_layer_rank_target = (unsigned)curr_layer_past_rank;
} else {
curr_layer_rank_target = (unsigned)past_layer_curr_rank;
}
}
// For the skipped layers, we need to add them to the damage region.
for (; past_layer_rank < past_layer_rank_target; past_layer_rank++) {
region_union_render_layer(damage, past_layer, past_layer_cmd);
past_layer_cmd += past_layer->number_of_commands;
past_layer += 1;
}
for (; curr_layer_rank < curr_layer_rank_target; curr_layer_rank++) {
region_union_render_layer(damage, curr_layer, curr_layer_cmd);
curr_layer_cmd += curr_layer->number_of_commands;
curr_layer += 1;
}
if (past_layer_rank >= past_layout->len || curr_layer_rank >= curr_layout->len) {
// No more matching layers left.
assert(past_layer_rank >= past_layout->len &&
curr_layer_rank >= curr_layout->len);
break;
}
assert(layer_key_eq(&past_layer->key, &curr_layer->key));
log_trace("%#010x == %#010x %s", past_layer->key.window,
curr_layer->key.window, curr_layer->win->name);
if (!layer_compare(past_layer, past_layer_cmd, curr_layer, curr_layer_cmd)) {
region_union_render_layer(damage, curr_layer, curr_layer_cmd);
region_union_render_layer(damage, past_layer, past_layer_cmd);
continue;
}
// Layers are otherwise identical besides the window content. We will
// process their render command and add appropriate damage.
log_trace("Adding window damage");
for (struct backend_command *cmd1 = past_layer_cmd, *cmd2 = curr_layer_cmd;
cmd1 < past_layer_cmd + past_layer->number_of_commands; cmd1++, cmd2++) {
switch (cmd1->op) {
case BACKEND_COMMAND_BLIT:
command_blit_damage(damage, &scratch_region, cmd1, cmd2,
lm, curr_layer_rank, buffer_age);
break;
case BACKEND_COMMAND_BLUR:
command_blur_damage(damage, &scratch_region, cmd1, cmd2,
blur_size);
break;
default: assert(false);
}
}
}
pixman_region32_fini(&scratch_region);
}

23
src/renderer/damage.h Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include "types.h"
typedef struct pixman_region32 region_t;
struct layout;
struct layout_manager;
/// Calculate damage of the screen for the last `buffer_age` layouts. Assuming the
/// current, yet to be rendered frame is numbered frame 0, the previous frame is numbered
/// frame -1, and so on. This function returns the region of the screen that will be
/// different between frame `-buffer_age` and frame 0. The region is in screen
/// coordinates. `buffer_age` is at least 1, and must be less than the `max_buffer_age`
/// passed to the `layout_manager_new` that was used to create `lm`.
///
/// The layouts you want to calculate damage for must already have commands built for
/// them. `blur_size` is the size of the background blur, and is assumed to not change
/// over time.
///
/// Note `layout_manager_damage` cannot take desktop background change into
/// account.
void layout_manager_damage(struct layout_manager *lm, unsigned buffer_age,
struct geometry blur_size, region_t *damage);

View file

@ -224,6 +224,52 @@ void layout_manager_append_layout(struct layout_manager *lm, struct wm *wm,
}
}
struct layout *layout_manager_layout(struct layout_manager *lm) {
return &lm->layouts[lm->current];
struct layout *layout_manager_layout(struct layout_manager *lm, unsigned age) {
if (age >= lm->max_buffer_age) {
assert(false);
return NULL;
}
return &lm->layouts[(lm->current + lm->max_buffer_age - age) % lm->max_buffer_age];
}
void layout_manager_collect_window_damage(const struct layout_manager *lm, unsigned index,
unsigned buffer_age, region_t *damage) {
auto curr = lm->current;
auto layer = &lm->layouts[curr].layers[index];
for (unsigned i = 0; i < buffer_age; i++) {
pixman_region32_union(damage, damage, &layer->damaged);
curr = (curr + lm->max_buffer_age - 1) % lm->max_buffer_age;
assert(layer->prev_rank >= 0);
layer = &lm->layouts[curr].layers[layer->prev_rank];
}
}
unsigned layout_manager_max_buffer_age(const struct layout_manager *lm) {
return lm->max_buffer_age - 1;
}
int layer_prev_rank(struct layout_manager *lm, unsigned buffer_age, unsigned index_) {
int index = to_int_checked(index_);
unsigned layout = lm->current;
while (buffer_age--) {
index = lm->layouts[layout].layers[index].prev_rank;
if (index < 0) {
break;
}
layout = (layout + lm->max_buffer_age - 1) % lm->max_buffer_age;
}
return index;
}
int layer_next_rank(struct layout_manager *lm, unsigned buffer_age, unsigned index_) {
int index = to_int_checked(index_);
unsigned layout = (lm->current + lm->max_buffer_age - buffer_age) % lm->max_buffer_age;
while (buffer_age--) {
index = lm->layouts[layout].layers[index].next_rank;
if (index < 0) {
break;
}
layout = (layout + 1) % lm->max_buffer_age;
}
return index;
}

View file

@ -102,7 +102,17 @@ struct layout_manager;
/// at the end of the ring buffer, and remove the oldest layout if the buffer is full.
void layout_manager_append_layout(struct layout_manager *lm, struct wm *wm,
struct geometry size);
struct layout *layout_manager_layout(struct layout_manager *lm);
/// Get the layout `age` frames into the past. Age `0` is the most recently appended
/// layout.
struct layout *layout_manager_layout(struct layout_manager *lm, unsigned age);
void layout_manager_free(struct layout_manager *lm);
/// Create a new render lm with a ring buffer for `max_buffer_age` layouts.
struct layout_manager *layout_manager_new(unsigned max_buffer_age);
/// Collect damage from the window for the past `buffer_age` frames.
void layout_manager_collect_window_damage(const struct layout_manager *lm, unsigned index,
unsigned buffer_age, region_t *damage);
/// Find where layer at `index` was `buffer_age` frames ago.
int layer_prev_rank(struct layout_manager *lm, unsigned buffer_age, unsigned index_);
/// Find layer that was at `index` `buffer_age` aga in the current layout.
int layer_next_rank(struct layout_manager *lm, unsigned buffer_age, unsigned index_);
unsigned layout_manager_max_buffer_age(const struct layout_manager *lm);

View file

@ -9,6 +9,7 @@
#include "backend/backend.h"
#include "backend/backend_common.h"
#include "command_builder.h"
#include "damage.h"
#include "layout.h"
struct renderer {
@ -18,6 +19,8 @@ struct renderer {
image_handle white_image;
/// 1x1 black image
image_handle black_image;
/// 1x1 image with the monitor repaint color
image_handle monitor_repaint_pixel;
/// 1x1 shadow colored xrender picture
xcb_render_picture_t shadow_pixel;
struct geometry canvas_size;
@ -39,6 +42,9 @@ void renderer_free(struct backend_base *backend, struct renderer *r) {
if (r->back_image) {
backend->ops->v2.release_image(backend, r->back_image);
}
if (r->monitor_repaint_pixel) {
backend->ops->v2.release_image(backend, r->monitor_repaint_pixel);
}
if (r->shadow_blur_context) {
backend->ops->destroy_blur_context(backend, r->shadow_blur_context);
}
@ -393,17 +399,26 @@ bool renderer_render(struct renderer *r, struct backend_base *backend,
image_handle root_image, struct layout_manager *lm,
struct command_builder *cb, void *blur_context,
uint64_t render_start_us, bool use_damage attr_unused,
bool monitor_repaint attr_unused, bool force_blend, bool blur_frame,
bool monitor_repaint, bool force_blend, bool blur_frame,
bool inactive_dim_fixed, double max_brightness, double inactive_dim,
const region_t *shadow_exclude, const struct x_monitors *monitors,
const struct win_option *wintype_options, uint64_t *after_damage_us) {
auto layout = layout_manager_layout(lm);
auto layout = layout_manager_layout(lm, 0);
if (!renderer_set_root_size(
r, backend, (struct geometry){layout->size.width, layout->size.height})) {
log_error("Failed to allocate back image");
return false;
}
if (monitor_repaint && r->monitor_repaint_pixel == NULL) {
r->monitor_repaint_pixel = backend->ops->v2.new_image(
backend, BACKEND_IMAGE_FORMAT_PIXMAP, (struct geometry){1, 1});
if (r->monitor_repaint_pixel) {
backend->ops->v2.clear(backend, r->monitor_repaint_pixel,
(struct color){.alpha = 0.5, .red = 0.5});
}
}
command_builder_build(cb, layout, force_blend, blur_frame, inactive_dim_fixed,
max_brightness, inactive_dim, shadow_exclude, monitors,
wintype_options);
@ -443,15 +458,42 @@ bool renderer_render(struct renderer *r, struct backend_base *backend,
return false;
}
region_t screen_region;
pixman_region32_init_rect(&screen_region, 0, 0, (unsigned)r->canvas_size.width,
(unsigned)r->canvas_size.height);
if (monitor_repaint && r->monitor_repaint_pixel) {
struct backend_mask mask = {};
pixman_region32_init(&mask.region);
pixman_region32_copy(&mask.region, &screen_region);
auto buffer_age = backend->ops->buffer_age(backend);
if (buffer_age > 0 &&
(unsigned)buffer_age <= layout_manager_max_buffer_age(lm)) {
struct geometry blur_size = {};
if (backend->ops->get_blur_size && blur_context) {
backend->ops->get_blur_size(
blur_context, &blur_size.width, &blur_size.height);
}
layout_manager_damage(lm, (unsigned)buffer_age, blur_size, &mask.region);
}
struct backend_blit_args blit = {
.source_image = r->monitor_repaint_pixel,
.max_brightness = 1,
.opacity = 1,
.ewidth = r->canvas_size.width,
.eheight = r->canvas_size.height,
.mask = &mask,
};
log_trace("Blit for monitor repaint");
backend->ops->v2.blit(backend, (struct coord){}, r->back_image, &blit);
pixman_region32_fini(&mask.region);
}
if (backend->ops->present) {
region_t screen_region;
pixman_region32_init_rect(&screen_region, 0, 0, (unsigned)r->canvas_size.width,
(unsigned)r->canvas_size.height);
backend->ops->v2.copy_area_quantize(backend, (struct coord){},
backend->ops->v2.back_buffer(backend),
r->back_image, &screen_region);
backend->ops->v2.present(backend);
pixman_region32_fini(&screen_region);
}
pixman_region32_fini(&screen_region);
return true;
}

View file

@ -5,6 +5,7 @@
/// Some common types
#include <stdbool.h>
#include <stdint.h>
/// Enumeration type to represent switches.
@ -39,5 +40,23 @@ typedef struct coord {
int x, y;
} coord_t;
static inline struct coord coord_add(struct coord a, struct coord b) {
return (struct coord){
.x = a.x + b.x,
.y = a.y + b.y,
};
}
static inline struct coord coord_sub(struct coord a, struct coord b) {
return (struct coord){
.x = a.x - b.x,
.y = a.y - b.y,
};
}
static inline bool coord_eq(struct coord a, struct coord b) {
return a.x == b.x && a.y == b.y;
}
#define MARGIN_INIT \
{ 0, 0, 0, 0 }