Sometimes a scheduled render can end up doing nothing, e.g. if the damage region is empty. In that case we don't have valid data to collect and thus shouldn't update the statistics. Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
537 lines
20 KiB
C
537 lines
20 KiB
C
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
|
#include <xcb/sync.h>
|
|
#include <xcb/xcb.h>
|
|
|
|
#include "backend/backend.h"
|
|
#include "common.h"
|
|
#include "compiler.h"
|
|
#include "config.h"
|
|
#include "log.h"
|
|
#include "region.h"
|
|
#include "types.h"
|
|
#include "win.h"
|
|
#include "x.h"
|
|
|
|
extern struct backend_operations xrender_ops, dummy_ops;
|
|
#ifdef CONFIG_OPENGL
|
|
extern struct backend_operations glx_ops;
|
|
extern struct backend_operations egl_ops;
|
|
#endif
|
|
|
|
struct backend_operations *backend_list[NUM_BKEND] = {
|
|
[BKEND_XRENDER] = &xrender_ops,
|
|
[BKEND_DUMMY] = &dummy_ops,
|
|
#ifdef CONFIG_OPENGL
|
|
[BKEND_GLX] = &glx_ops,
|
|
[BKEND_EGL] = &egl_ops,
|
|
#endif
|
|
};
|
|
|
|
/**
|
|
* @param all_damage if true ignore damage and repaint the whole screen
|
|
*/
|
|
region_t get_damage(session_t *ps, bool all_damage) {
|
|
region_t region;
|
|
auto buffer_age_fn = ps->backend_data->ops->buffer_age;
|
|
int buffer_age = buffer_age_fn ? buffer_age_fn(ps->backend_data) : -1;
|
|
|
|
if (all_damage) {
|
|
buffer_age = -1;
|
|
}
|
|
|
|
pixman_region32_init(®ion);
|
|
if (buffer_age == -1 || buffer_age > ps->ndamage) {
|
|
pixman_region32_copy(®ion, &ps->screen_reg);
|
|
} else {
|
|
for (int i = 0; i < buffer_age; i++) {
|
|
auto curr = ((ps->damage - ps->damage_ring) + i) % ps->ndamage;
|
|
log_trace("damage index: %d, damage ring offset: %td", i, curr);
|
|
dump_region(&ps->damage_ring[curr]);
|
|
pixman_region32_union(®ion, ®ion, &ps->damage_ring[curr]);
|
|
}
|
|
pixman_region32_intersect(®ion, ®ion, &ps->screen_reg);
|
|
}
|
|
return region;
|
|
}
|
|
|
|
void handle_device_reset(session_t *ps) {
|
|
log_error("Device reset detected");
|
|
// Wait for reset to complete
|
|
// Although ideally the backend should return DEVICE_STATUS_NORMAL after a reset
|
|
// is completed, it's not always possible.
|
|
//
|
|
// According to ARB_robustness (emphasis mine):
|
|
//
|
|
// "If a reset status other than NO_ERROR is returned and subsequent
|
|
// calls return NO_ERROR, the context reset was encountered and
|
|
// completed. If a reset status is repeatedly returned, the context **may**
|
|
// be in the process of resetting."
|
|
//
|
|
// Which means it may also not be in the process of resetting. For example on
|
|
// AMDGPU devices, Mesa OpenGL always return CONTEXT_RESET after a reset has
|
|
// started, completed or not.
|
|
//
|
|
// So here we blindly wait 5 seconds and hope ourselves best of the luck.
|
|
sleep(5);
|
|
|
|
// Reset picom
|
|
log_info("Resetting picom after device reset");
|
|
ev_break(ps->loop, EVBREAK_ALL);
|
|
}
|
|
|
|
/// paint all windows
|
|
void paint_all_new(session_t *ps, struct managed_win *t) {
|
|
if (ps->backend_data->ops->device_status &&
|
|
ps->backend_data->ops->device_status(ps->backend_data) != DEVICE_STATUS_NORMAL) {
|
|
return handle_device_reset(ps);
|
|
}
|
|
if (ps->o.xrender_sync_fence) {
|
|
if (ps->xsync_exists && !x_fence_sync(ps->c, ps->sync_fence)) {
|
|
log_error("x_fence_sync failed, xrender-sync-fence will be "
|
|
"disabled from now on.");
|
|
xcb_sync_destroy_fence(ps->c, ps->sync_fence);
|
|
ps->sync_fence = XCB_NONE;
|
|
ps->o.xrender_sync_fence = false;
|
|
ps->xsync_exists = false;
|
|
}
|
|
}
|
|
// All painting will be limited to the damage, if _some_ of
|
|
// the paints bleed out of the damage region, it will destroy
|
|
// part of the image we want to reuse
|
|
region_t reg_damage;
|
|
reg_damage = get_damage(ps, ps->o.monitor_repaint || !ps->o.use_damage);
|
|
|
|
if (!pixman_region32_not_empty(®_damage)) {
|
|
pixman_region32_fini(®_damage);
|
|
return;
|
|
}
|
|
|
|
#ifdef DEBUG_REPAINT
|
|
static struct timespec last_paint = {0};
|
|
#endif
|
|
|
|
// <damage-note>
|
|
// If use_damage is enabled, we MUST make sure only the damaged regions of the
|
|
// screen are ever touched by the compositor. The reason is that at the beginning
|
|
// of each render, we clear the damaged regions with the wallpaper, and nothing
|
|
// else. If later during the render we changed anything outside the damaged
|
|
// region, that won't be cleared by the next render, and will thus accumulate.
|
|
// (e.g. if shadow is drawn outside the damaged region, it will become thicker and
|
|
// thicker over time.)
|
|
|
|
/// The adjusted damaged regions
|
|
region_t reg_paint;
|
|
assert(ps->o.blur_method != BLUR_METHOD_INVALID);
|
|
if (ps->o.blur_method != BLUR_METHOD_NONE && ps->backend_data->ops->get_blur_size) {
|
|
int blur_width, blur_height;
|
|
ps->backend_data->ops->get_blur_size(ps->backend_blur_context,
|
|
&blur_width, &blur_height);
|
|
|
|
// The region of screen a given window influences will be smeared
|
|
// out by blur. With more windows on top of the given window, the
|
|
// influences region will be smeared out more.
|
|
//
|
|
// Also, blurring requires data slightly outside the area that needs
|
|
// to be blurred. The more semi-transparent windows are stacked on top
|
|
// of each other, the larger the area will be.
|
|
//
|
|
// Instead of accurately calculate how much bigger the damage
|
|
// region will be because of blur, we assume the worst case here.
|
|
// That is, the damaged window is at the bottom of the stack, and
|
|
// all other windows have semi-transparent background
|
|
//
|
|
// TODO(yshui): maybe we don't need to resize reg_damage, only reg_paint?
|
|
int resize_factor = 1;
|
|
if (t) {
|
|
resize_factor = t->stacking_rank;
|
|
}
|
|
resize_region_in_place(®_damage, blur_width * resize_factor,
|
|
blur_height * resize_factor);
|
|
reg_paint = resize_region(®_damage, blur_width * resize_factor,
|
|
blur_height * resize_factor);
|
|
pixman_region32_intersect(®_paint, ®_paint, &ps->screen_reg);
|
|
pixman_region32_intersect(®_damage, ®_damage, &ps->screen_reg);
|
|
} else {
|
|
pixman_region32_init(®_paint);
|
|
pixman_region32_copy(®_paint, ®_damage);
|
|
}
|
|
|
|
// A hint to backend, the region that will be visible on screen
|
|
// backend can optimize based on this info
|
|
region_t reg_visible;
|
|
pixman_region32_init(®_visible);
|
|
pixman_region32_copy(®_visible, &ps->screen_reg);
|
|
if (t && !ps->o.transparent_clipping) {
|
|
// Calculate the region upon which the root window (wallpaper) is to be
|
|
// painted based on the ignore region of the lowest window, if available
|
|
//
|
|
// NOTE If transparent_clipping is enabled, transparent windows are
|
|
// included in the reg_ignore, but we still want to have the wallpaper
|
|
// beneath them, so we don't use reg_ignore for wallpaper in that case.
|
|
pixman_region32_subtract(®_visible, ®_visible, t->reg_ignore);
|
|
}
|
|
|
|
// Region on screen we don't want any shadows on
|
|
region_t reg_shadow_clip;
|
|
pixman_region32_init(®_shadow_clip);
|
|
|
|
struct timespec now;
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
auto now_us = (uint64_t)(now.tv_sec * 1000000 + now.tv_nsec / 1000);
|
|
if (ps->next_render > 0) {
|
|
log_trace("Render schedule deviation: %ld us (%s) %ld %ld",
|
|
labs((int64_t)now_us - (int64_t)ps->next_render),
|
|
now_us < ps->next_render ? "early" : "late", now_us,
|
|
ps->next_render);
|
|
ps->last_schedule_delay = 0;
|
|
if (now_us > ps->next_render) {
|
|
ps->last_schedule_delay = now_us - ps->next_render;
|
|
}
|
|
}
|
|
ps->did_render = true;
|
|
|
|
if (ps->backend_data->ops->prepare) {
|
|
ps->backend_data->ops->prepare(ps->backend_data, ®_paint);
|
|
}
|
|
|
|
if (ps->root_image) {
|
|
ps->backend_data->ops->compose(ps->backend_data, ps->root_image,
|
|
(coord_t){0}, NULL, (coord_t){0},
|
|
®_paint, ®_visible);
|
|
} else {
|
|
ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1},
|
|
®_paint);
|
|
}
|
|
|
|
// Windows are sorted from bottom to top
|
|
// Each window has a reg_ignore, which is the region obscured by all the windows
|
|
// on top of that window. This is used to reduce the number of pixels painted.
|
|
//
|
|
// Whether this is beneficial is to be determined XXX
|
|
for (auto w = t; w; w = w->prev_trans) {
|
|
pixman_region32_subtract(®_visible, &ps->screen_reg, w->reg_ignore);
|
|
assert(!(w->flags & WIN_FLAGS_IMAGE_ERROR));
|
|
assert(!(w->flags & WIN_FLAGS_PIXMAP_STALE));
|
|
assert(!(w->flags & WIN_FLAGS_PIXMAP_NONE));
|
|
|
|
// The bounding shape of the window, in global/target coordinates
|
|
// reminder: bounding shape contains the WM frame
|
|
auto reg_bound = win_get_bounding_shape_global_by_val(w);
|
|
auto reg_bound_no_corner =
|
|
win_get_bounding_shape_global_without_corners_by_val(w);
|
|
|
|
if (!w->mask_image && (w->bounding_shaped || w->corner_radius != 0)) {
|
|
win_bind_mask(ps->backend_data, w);
|
|
}
|
|
|
|
// The clip region for the current window, in global/target coordinates
|
|
// reg_paint_in_bound \in reg_paint
|
|
region_t reg_paint_in_bound;
|
|
pixman_region32_init(®_paint_in_bound);
|
|
pixman_region32_intersect(®_paint_in_bound, ®_bound, ®_paint);
|
|
if (ps->o.transparent_clipping) {
|
|
// <transparent-clipping-note>
|
|
// If transparent_clipping is enabled, we need to be SURE that
|
|
// things are not drawn inside reg_ignore, because otherwise they
|
|
// will appear underneath transparent windows.
|
|
// So here we have make sure reg_paint_in_bound \in reg_visible
|
|
// There are a few other places below where this is needed as
|
|
// well.
|
|
pixman_region32_intersect(®_paint_in_bound,
|
|
®_paint_in_bound, ®_visible);
|
|
}
|
|
|
|
// Blur window background
|
|
/* TODO(yshui) since the backend might change the content of the window
|
|
* (e.g. with shaders), we should consult the backend whether the window
|
|
* is transparent or not. for now we will just rely on the force_win_blend
|
|
* option */
|
|
auto real_win_mode = w->mode;
|
|
coord_t window_coord = {.x = w->g.x, .y = w->g.y};
|
|
|
|
if (w->blur_background &&
|
|
(ps->o.force_win_blend || real_win_mode == WMODE_TRANS ||
|
|
(ps->o.blur_background_frame && real_win_mode == WMODE_FRAME_TRANS))) {
|
|
// Minimize the region we try to blur, if the window
|
|
// itself is not opaque, only the frame is.
|
|
|
|
double blur_opacity = 1;
|
|
if (w->opacity < (1.0 / MAX_ALPHA)) {
|
|
// Hide blur for fully transparent windows.
|
|
blur_opacity = 0;
|
|
} else if (w->state == WSTATE_MAPPING) {
|
|
// Gradually increase the blur intensity during
|
|
// fading in.
|
|
assert(w->opacity <= w->opacity_target);
|
|
blur_opacity = w->opacity / w->opacity_target;
|
|
} else if (w->state == WSTATE_UNMAPPING ||
|
|
w->state == WSTATE_DESTROYING) {
|
|
// Gradually decrease the blur intensity during
|
|
// fading out.
|
|
assert(w->opacity <= w->opacity_target_old);
|
|
blur_opacity = w->opacity / w->opacity_target_old;
|
|
} else if (w->state == WSTATE_FADING) {
|
|
if (w->opacity < w->opacity_target &&
|
|
w->opacity_target_old < (1.0 / MAX_ALPHA)) {
|
|
// Gradually increase the blur intensity during
|
|
// fading in.
|
|
assert(w->opacity <= w->opacity_target);
|
|
blur_opacity = w->opacity / w->opacity_target;
|
|
} else if (w->opacity > w->opacity_target &&
|
|
w->opacity_target < (1.0 / MAX_ALPHA)) {
|
|
// Gradually decrease the blur intensity during
|
|
// fading out.
|
|
assert(w->opacity <= w->opacity_target_old);
|
|
blur_opacity = w->opacity / w->opacity_target_old;
|
|
}
|
|
}
|
|
assert(blur_opacity >= 0 && blur_opacity <= 1);
|
|
|
|
if (real_win_mode == WMODE_TRANS || ps->o.force_win_blend) {
|
|
// We need to blur the bounding shape of the window
|
|
// (reg_paint_in_bound = reg_bound \cap reg_paint)
|
|
ps->backend_data->ops->blur(
|
|
ps->backend_data, blur_opacity,
|
|
ps->backend_blur_context, w->mask_image, window_coord,
|
|
®_paint_in_bound, ®_visible);
|
|
} else {
|
|
// Window itself is solid, we only need to blur the frame
|
|
// region
|
|
|
|
// Readability assertions
|
|
assert(ps->o.blur_background_frame);
|
|
assert(real_win_mode == WMODE_FRAME_TRANS);
|
|
|
|
auto reg_blur = win_get_region_frame_local_by_val(w);
|
|
pixman_region32_translate(®_blur, w->g.x, w->g.y);
|
|
// make sure reg_blur \in reg_paint
|
|
pixman_region32_intersect(®_blur, ®_blur, ®_paint);
|
|
if (ps->o.transparent_clipping) {
|
|
// ref: <transparent-clipping-note>
|
|
pixman_region32_intersect(®_blur, ®_blur,
|
|
®_visible);
|
|
}
|
|
ps->backend_data->ops->blur(
|
|
ps->backend_data, blur_opacity, ps->backend_blur_context,
|
|
w->mask_image, window_coord, ®_blur, ®_visible);
|
|
pixman_region32_fini(®_blur);
|
|
}
|
|
}
|
|
|
|
// Draw shadow on target
|
|
if (w->shadow) {
|
|
assert(!(w->flags & WIN_FLAGS_SHADOW_NONE));
|
|
// Clip region for the shadow
|
|
// reg_shadow \in reg_paint
|
|
auto reg_shadow = win_extents_by_val(w);
|
|
pixman_region32_intersect(®_shadow, ®_shadow, ®_paint);
|
|
|
|
// Mask out the region we don't want shadow on
|
|
if (pixman_region32_not_empty(&ps->shadow_exclude_reg)) {
|
|
pixman_region32_subtract(®_shadow, ®_shadow,
|
|
&ps->shadow_exclude_reg);
|
|
}
|
|
if (pixman_region32_not_empty(®_shadow_clip)) {
|
|
pixman_region32_subtract(®_shadow, ®_shadow,
|
|
®_shadow_clip);
|
|
}
|
|
|
|
if (ps->o.crop_shadow_to_monitor && w->randr_monitor >= 0 &&
|
|
w->randr_monitor < ps->randr_nmonitors) {
|
|
// There can be a window where number of monitors is
|
|
// updated, but the monitor number attached to the window
|
|
// have not.
|
|
//
|
|
// Window monitor number will be updated eventually, so
|
|
// here we just check to make sure we don't access out of
|
|
// bounds.
|
|
pixman_region32_intersect(
|
|
®_shadow, ®_shadow,
|
|
&ps->randr_monitor_regs[w->randr_monitor]);
|
|
}
|
|
|
|
if (ps->o.transparent_clipping) {
|
|
// ref: <transparent-clipping-note>
|
|
pixman_region32_intersect(®_shadow, ®_shadow,
|
|
®_visible);
|
|
}
|
|
|
|
assert(w->shadow_image);
|
|
ps->backend_data->ops->set_image_property(
|
|
ps->backend_data, IMAGE_PROPERTY_OPACITY, w->shadow_image,
|
|
&w->opacity);
|
|
coord_t shadow_coord = {.x = w->g.x + w->shadow_dx,
|
|
.y = w->g.y + w->shadow_dy};
|
|
|
|
auto inverted_mask = NULL;
|
|
if (!ps->o.wintype_option[w->window_type].full_shadow) {
|
|
pixman_region32_subtract(®_shadow, ®_shadow,
|
|
®_bound_no_corner);
|
|
if (w->mask_image) {
|
|
inverted_mask = w->mask_image;
|
|
ps->backend_data->ops->set_image_property(
|
|
ps->backend_data, IMAGE_PROPERTY_INVERTED,
|
|
inverted_mask, (bool[]){true});
|
|
}
|
|
}
|
|
ps->backend_data->ops->compose(
|
|
ps->backend_data, w->shadow_image, shadow_coord,
|
|
inverted_mask, window_coord, ®_shadow, ®_visible);
|
|
if (inverted_mask) {
|
|
ps->backend_data->ops->set_image_property(
|
|
ps->backend_data, IMAGE_PROPERTY_INVERTED,
|
|
inverted_mask, (bool[]){false});
|
|
}
|
|
pixman_region32_fini(®_shadow);
|
|
}
|
|
|
|
// Update image properties
|
|
{
|
|
double dim_opacity = 0.0;
|
|
if (w->dim) {
|
|
dim_opacity = ps->o.inactive_dim;
|
|
if (!ps->o.inactive_dim_fixed) {
|
|
dim_opacity *= w->opacity;
|
|
}
|
|
}
|
|
|
|
ps->backend_data->ops->set_image_property(
|
|
ps->backend_data, IMAGE_PROPERTY_MAX_BRIGHTNESS, w->win_image,
|
|
&ps->o.max_brightness);
|
|
ps->backend_data->ops->set_image_property(
|
|
ps->backend_data, IMAGE_PROPERTY_INVERTED, w->win_image,
|
|
&w->invert_color);
|
|
ps->backend_data->ops->set_image_property(
|
|
ps->backend_data, IMAGE_PROPERTY_DIM_LEVEL, w->win_image,
|
|
&dim_opacity);
|
|
ps->backend_data->ops->set_image_property(
|
|
ps->backend_data, IMAGE_PROPERTY_OPACITY, w->win_image, &w->opacity);
|
|
ps->backend_data->ops->set_image_property(
|
|
ps->backend_data, IMAGE_PROPERTY_CORNER_RADIUS, w->win_image,
|
|
(double[]){w->corner_radius});
|
|
if (w->corner_radius) {
|
|
int border_width = w->g.border_width;
|
|
if (border_width == 0) {
|
|
// Some WM has borders implemented as WM frames
|
|
border_width = min3(w->frame_extents.left,
|
|
w->frame_extents.right,
|
|
w->frame_extents.bottom);
|
|
}
|
|
ps->backend_data->ops->set_image_property(
|
|
ps->backend_data, IMAGE_PROPERTY_BORDER_WIDTH,
|
|
w->win_image, &border_width);
|
|
}
|
|
|
|
ps->backend_data->ops->set_image_property(
|
|
ps->backend_data, IMAGE_PROPERTY_CUSTOM_SHADER, w->win_image,
|
|
w->fg_shader ? (void *)w->fg_shader->backend_shader : NULL);
|
|
}
|
|
|
|
if (w->opacity * MAX_ALPHA < 1) {
|
|
// We don't need to paint the window body itself if it's
|
|
// completely transparent.
|
|
goto skip;
|
|
}
|
|
|
|
if (w->clip_shadow_above) {
|
|
// Add window bounds to shadow-clip region
|
|
pixman_region32_union(®_shadow_clip, ®_shadow_clip, ®_bound);
|
|
} else {
|
|
// Remove overlapping window bounds from shadow-clip region
|
|
pixman_region32_subtract(®_shadow_clip, ®_shadow_clip, ®_bound);
|
|
}
|
|
|
|
// Draw window on target
|
|
if (w->frame_opacity == 1) {
|
|
ps->backend_data->ops->compose(ps->backend_data, w->win_image,
|
|
window_coord, NULL, window_coord,
|
|
®_paint_in_bound, ®_visible);
|
|
} else {
|
|
// For window image processing, we don't have to limit the process
|
|
// region to damage for correctness. (see <damager-note> for
|
|
// details)
|
|
|
|
// The visible region, in window local coordinates Although we
|
|
// don't limit process region to damage, we provide that info in
|
|
// reg_visible as a hint. Since window image data outside of the
|
|
// damage region won't be painted onto target
|
|
region_t reg_visible_local;
|
|
region_t reg_bound_local;
|
|
{
|
|
// The bounding shape, in window local coordinates
|
|
pixman_region32_init(®_bound_local);
|
|
pixman_region32_copy(®_bound_local, ®_bound);
|
|
pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y);
|
|
|
|
pixman_region32_init(®_visible_local);
|
|
pixman_region32_intersect(®_visible_local,
|
|
®_visible, ®_paint);
|
|
pixman_region32_translate(®_visible_local, -w->g.x,
|
|
-w->g.y);
|
|
// Data outside of the bounding shape won't be visible,
|
|
// but it is not necessary to limit the image operations
|
|
// to the bounding shape yet. So pass that as the visible
|
|
// region, not the clip region.
|
|
pixman_region32_intersect(
|
|
®_visible_local, ®_visible_local, ®_bound_local);
|
|
}
|
|
|
|
auto new_img = ps->backend_data->ops->clone_image(
|
|
ps->backend_data, w->win_image, ®_visible_local);
|
|
auto reg_frame = win_get_region_frame_local_by_val(w);
|
|
ps->backend_data->ops->image_op(
|
|
ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, ®_frame,
|
|
®_visible_local, (double[]){w->frame_opacity});
|
|
pixman_region32_fini(®_frame);
|
|
ps->backend_data->ops->compose(ps->backend_data, new_img,
|
|
window_coord, NULL, window_coord,
|
|
®_paint_in_bound, ®_visible);
|
|
ps->backend_data->ops->release_image(ps->backend_data, new_img);
|
|
pixman_region32_fini(®_visible_local);
|
|
pixman_region32_fini(®_bound_local);
|
|
}
|
|
skip:
|
|
pixman_region32_fini(®_bound);
|
|
pixman_region32_fini(®_bound_no_corner);
|
|
pixman_region32_fini(®_paint_in_bound);
|
|
}
|
|
pixman_region32_fini(®_paint);
|
|
pixman_region32_fini(®_shadow_clip);
|
|
|
|
if (ps->o.monitor_repaint) {
|
|
const struct color DEBUG_COLOR = {0.5, 0, 0, 0.5};
|
|
auto reg_damage_debug = get_damage(ps, false);
|
|
ps->backend_data->ops->fill(ps->backend_data, DEBUG_COLOR, ®_damage_debug);
|
|
pixman_region32_fini(®_damage_debug);
|
|
}
|
|
|
|
// Move the head of the damage ring
|
|
ps->damage = ps->damage - 1;
|
|
if (ps->damage < ps->damage_ring) {
|
|
ps->damage = ps->damage_ring + ps->ndamage - 1;
|
|
}
|
|
pixman_region32_clear(ps->damage);
|
|
|
|
if (ps->backend_data->ops->present) {
|
|
// Present the rendered scene
|
|
// Vsync is done here
|
|
ps->backend_data->ops->present(ps->backend_data, ®_damage);
|
|
}
|
|
|
|
pixman_region32_fini(®_damage);
|
|
|
|
#ifdef DEBUG_REPAINT
|
|
struct timespec now = get_time_timespec();
|
|
struct timespec diff = {0};
|
|
timespec_subtract(&diff, &now, &last_paint);
|
|
log_trace("[ %5ld:%09ld ] ", diff.tv_sec, diff.tv_nsec);
|
|
last_paint = now;
|
|
log_trace("paint:");
|
|
for (win *w = t; w; w = w->prev_trans)
|
|
log_trace(" %#010lx", w->id);
|
|
#endif
|
|
}
|
|
|
|
// vim: set noet sw=8 ts=8 :
|