Skip to content

Commit

Permalink
win: switch to use the new animation system
Browse files Browse the repository at this point in the history
Remove the old fading machinary. As a side-effect, animation and time
based shaders are now unified.

Signed-off-by: Yuxuan Shui <[email protected]>
  • Loading branch information
yshui committed May 9, 2024
1 parent a95696c commit 9f5da3a
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 340 deletions.
4 changes: 2 additions & 2 deletions src/dbus.c
Original file line number Diff line number Diff line change
Expand Up @@ -708,13 +708,13 @@ cdbus_process_win_get(session_t *ps, DBusMessage *msg, DBusMessage *reply, DBusE
append(map_state, boolean, w->a.map_state);
append(wmwin, boolean, win_is_wmwin(w));
append(focused_raw, boolean, win_is_focused_raw(w));
append(opacity, double, animatable_get(&w->opacity));
append(left_width, int32, w->frame_extents.left);
append(right_width, int32, w->frame_extents.right);
append(top_width, int32, w->frame_extents.top);
append(bottom_width, int32, w->frame_extents.bottom);

append_win_property(mode, enum);
append_win_property(opacity, double);
append_win_property(client_win, wid);
append_win_property(ever_damaged, boolean);
append_win_property(window_type, enum);
Expand All @@ -727,7 +727,7 @@ cdbus_process_win_get(session_t *ps, DBusMessage *msg, DBusMessage *reply, DBusE
append_win_property(class_instance, string);
append_win_property(class_general, string);
append_win_property(role, string);
append_win_property(opacity.target, double);
append_win_property(opacity, double);
append_win_property(has_opacity_prop, boolean);
append_win_property(opacity_prop, uint32);
append_win_property(opacity_is_set, boolean);
Expand Down
17 changes: 6 additions & 11 deletions src/event.c
Original file line number Diff line number Diff line change
Expand Up @@ -306,12 +306,8 @@ static inline void ev_destroy_notify(session_t *ps, xcb_destroy_notify_event_t *

if (w != NULL) {
destroy_win_start(ps, w);
if (!w->managed || !win_as_managed(w)->to_paint) {
// If the window wasn't managed, or was already not rendered,
// we don't need to fade it out.
if (w->managed) {
win_skip_fading(win_as_managed(w));
}
if (!w->managed) {
// If the window wasn't managed, we can release it immediately
destroy_win_finish(ps, w);
}
return;
Expand Down Expand Up @@ -362,7 +358,7 @@ static inline void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) {
static inline void ev_unmap_notify(session_t *ps, xcb_unmap_notify_event_t *ev) {
auto w = wm_find_managed(ps->wm, ev->window);
if (w) {
unmap_win_start(ps, w);
unmap_win_start(w);
}
}

Expand Down Expand Up @@ -441,13 +437,10 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t
// Emulating what X server does: a destroyed
// window is always unmapped first.
if (mw->state == WSTATE_MAPPED) {
unmap_win_start(ps, mw);
unmap_win_start(mw);
}
}
destroy_win_start(ps, old_w);
if (old_w->managed) {
win_skip_fading(win_as_managed(old_w));
}
destroy_win_finish(ps, old_w);
}

Expand Down Expand Up @@ -677,6 +670,8 @@ static inline void repair_win(session_t *ps, struct managed_win *w) {
free(e);
}
win_extents(w, &parts);
log_debug("Window %#010x (%s) has been damaged the first time",
w->base.id, w->name);
} else {
auto cookie = xcb_damage_subtract(ps->c.c, w->damage, XCB_NONE,
ps->damage_ring.x_region);
Expand Down
145 changes: 41 additions & 104 deletions src/picom.c
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,10 @@ void quit(session_t *ps) {
}

/**
* Get current system clock in milliseconds.
* Convert struct timespec to milliseconds.
*/
static inline int64_t get_time_ms(void) {
struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp);
return (int64_t)tp.tv_sec * 1000 + (int64_t)tp.tv_nsec / 1000000;
static inline int64_t timespec_ms(struct timespec ts) {
return (int64_t)ts.tv_sec * 1000 + (int64_t)ts.tv_nsec / 1000000;
}

enum vblank_callback_action check_render_finish(struct vblank_event *e attr_unused, void *ud) {
Expand Down Expand Up @@ -452,55 +450,6 @@ void add_damage(session_t *ps, const region_t *damage) {
pixman_region32_union(cursor, cursor, (region_t *)damage);
}

// === Fading ===

/**
* Get the time left before next fading point.
*
* In milliseconds.
*/
static double fade_timeout(session_t *ps) {
auto now = get_time_ms();
if (ps->o.fade_delta + ps->fade_time < now) {
return 0;
}

auto diff = ps->o.fade_delta + ps->fade_time - now;

diff = clamp(diff, 0, ps->o.fade_delta * 2);

return (double)diff / 1000.0;
}

/**
* Run fading on a window.
*
* @param steps steps of fading
* @return whether we are still in fading mode
*/
static bool run_fade(struct managed_win **_w, double delta_sec) {
auto w = *_w;
log_trace("Process fading for window %s (%#010x), ΔT: %fs", w->name, w->base.id,
delta_sec);
if (w->number_of_animations == 0) {
// We have reached target opacity.
// We don't call win_check_fade_finished here because that could destroy
// the window, but we still need the damage info from this window
log_trace("|- was fading but finished");
return false;
}

log_trace("|- fading, opacity: %lf", animatable_get(&w->opacity));
animatable_advance(&w->opacity, delta_sec);
animatable_advance(&w->blur_opacity, delta_sec);
log_trace("|- opacity updated: %lf", animatable_get(&w->opacity));

// Note even if the animatable is not animating anymore at this point, we still
// want to run preprocess one last time to finish state transition. So return true
// in that case too.
return true;
}

// === Windows ===

/**
Expand Down Expand Up @@ -607,8 +556,9 @@ static void rebuild_shadow_exclude_reg(session_t *ps) {
/// Free up all the images and deinit the backend
static void destroy_backend(session_t *ps) {
win_stack_foreach_managed_safe(w, wm_stack_end(ps->wm)) {
// Wrapping up fading in progress
win_skip_fading(w);
// Wrapping up animation in progress
free(w->running_animation);
w->running_animation = NULL;

if (ps->backend_data) {
// Unmapped windows could still have shadow images, but not pixmap
Expand Down Expand Up @@ -878,24 +828,13 @@ static void handle_root_flags(session_t *ps) {
*
* @return whether the operation succeeded
*/
static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
struct managed_win **out_bottom) {
static bool paint_preprocess(session_t *ps, bool *animation, struct managed_win **out_bottom) {
// XXX need better, more general name for `fade_running`. It really
// means if fade is still ongoing after the current frame is rendered
struct managed_win *bottom = NULL;
*fade_running = false;
*animation = false;
*out_bottom = NULL;

// Fading step calculation
int64_t delta_ms = 0L;
auto now = get_time_ms();
if (ps->fade_time) {
assert(now >= ps->fade_time);
delta_ms = now - ps->fade_time;
}
ps->fade_time = now;

// First, let's process fading, and animated shaders
// TODO(yshui) check if a window is fully obscured, and if we don't need to
// process fading or animation for it.
Expand All @@ -912,20 +851,18 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
add_damage_from_win(ps, w);
*animation = true;
}
if (w->running_animation != NULL) {
*animation = true;
}

// Add window to damaged area if its opacity changes
// If was_painted == false, and to_paint is also false, we don't care
// If was_painted == false, but to_paint is true, damage will be added in
// the loop below
if (was_painted && w->number_of_animations != 0) {
if (was_painted && w->running_animation != NULL) {
add_damage_from_win(ps, w);
}

// Run fading
if (run_fade(&w, (double)delta_ms / 1000.0)) {
*fade_running = true;
}

if (win_has_frame(w)) {
w->frame_opacity = ps->o.frame_opacity;
} else {
Expand Down Expand Up @@ -954,8 +891,8 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
bool to_paint = true;
// w->to_paint remembers whether this window is painted last time
const bool was_painted = w->to_paint;
const double window_opacity = animatable_get(&w->opacity);
const double blur_opacity = animatable_get(&w->blur_opacity);
const double window_opacity = win_animatable_get(w, WIN_SCRIPT_OPACITY);
const double blur_opacity = win_animatable_get(w, WIN_SCRIPT_BLUR_OPACITY);

// Destroy reg_ignore if some window above us invalidated it
if (!reg_ignore_valid) {
Expand All @@ -970,12 +907,7 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
// pixmap is gone (for example due to a ConfigureNotify), or when it's
// excluded
if ((w->state == WSTATE_UNMAPPED || w->state == WSTATE_DESTROYED) &&
w->number_of_animations == 0) {
if (window_opacity != 0 || blur_opacity != 0) {
log_warn("Window %#010x (%s) is unmapped but still has "
"opacity",
w->base.id, w->name);
}
w->running_animation == NULL) {
log_trace("|- is unmapped");
to_paint = false;
} else if (unlikely(ps->debug_window != XCB_NONE) &&
Expand Down Expand Up @@ -1023,9 +955,6 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
log_trace("|- will be painted");
log_verbose("Window %#010x (%s) will be painted", w->base.id, w->name);

// Calculate shadow opacity
w->shadow_opacity = ps->o.shadow_opacity * window_opacity * ps->o.frame_opacity;

// Generate ignore region for painting to reduce GPU load
if (!w->reg_ignore) {
w->reg_ignore = rc_region_ref(last_reg_ignore);
Expand Down Expand Up @@ -1094,7 +1023,7 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
reg_ignore_valid = reg_ignore_valid && w->reg_ignore_valid;
w->reg_ignore_valid = true;

if (w->state == WSTATE_DESTROYED && w->number_of_animations == 0) {
if (w->state == WSTATE_DESTROYED && w->running_animation == NULL) {
// the window should be destroyed because it was destroyed
// by X server and now its animations are finished
destroy_win_finish(ps, &w->base);
Expand Down Expand Up @@ -1697,7 +1626,8 @@ static void fade_timer_callback(EV_P attr_unused, ev_timer *w, int revents attr_
queue_redraw(ps);
}

static void handle_pending_updates(EV_P_ struct session *ps) {
static void handle_pending_updates(EV_P_ struct session *ps, double delta_t) {
log_trace("Delayed handling of events, entering critical section");
auto e = xcb_request_check(ps->c.c, xcb_grab_server_checked(ps->c.c));
if (e) {
log_fatal_x_error(e, "failed to grab x server");
Expand Down Expand Up @@ -1738,6 +1668,12 @@ static void handle_pending_updates(EV_P_ struct session *ps) {
ps->server_grabbed = false;
ps->pending_updates = false;
log_trace("Exited critical section");

win_stack_foreach_managed_safe(w, wm_stack_end(ps->wm)) {
// Window might be freed by this function, if it's destroyed and its
// animation finished
win_process_animation_and_state_change(ps, w, delta_t);
}
}

static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
Expand All @@ -1748,13 +1684,22 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
int64_t draw_callback_enter_us;
clock_gettime(CLOCK_MONOTONIC, &now);

// Fading step calculation
auto now_ms = timespec_ms(now);
int64_t delta_ms = 0L;
if (ps->fade_time) {
assert(now_ms >= ps->fade_time);
delta_ms = now_ms - ps->fade_time;
}
ps->fade_time = now_ms;

draw_callback_enter_us = (now.tv_sec * 1000000LL + now.tv_nsec / 1000);
if (ps->next_render != 0) {
log_trace("Schedule delay: %" PRIi64 " us",
draw_callback_enter_us - (int64_t)ps->next_render);
}

handle_pending_updates(EV_A_ ps);
handle_pending_updates(EV_A_ ps, (double)delta_ms / 1000.0);

int64_t after_handle_pending_updates_us;
clock_gettime(CLOCK_MONOTONIC, &now);
Expand All @@ -1771,8 +1716,12 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
//
// Using foreach_safe here since skipping fading can cause window to be
// freed if it's destroyed.
//
// TODO(yshui) I think maybe we don't need this anymore, since now we
// immediate acquire pixmap right after `map_win_start`.
win_stack_foreach_managed_safe(w, wm_stack_end(ps->wm)) {
win_skip_fading(w);
free(w->running_animation);
w->running_animation = NULL;
if (w->state == WSTATE_DESTROYED) {
destroy_win_finish(ps, &w->base);
}
Expand All @@ -1795,11 +1744,10 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
/* TODO(yshui) Have a stripped down version of paint_preprocess that is used when
* screen is not redirected. its sole purpose should be to decide whether the
* screen should be redirected. */
bool fade_running = false;
bool animation = false;
bool was_redirected = ps->redirected;
struct managed_win *bottom = NULL;
if (!paint_preprocess(ps, &fade_running, &animation, &bottom)) {
if (!paint_preprocess(ps, &animation, &bottom)) {
log_fatal("Pre-render preparation has failed, exiting...");
exit(1);
}
Expand All @@ -1818,14 +1766,6 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
return draw_callback_impl(EV_A_ ps, revents);
}

// Start/stop fade timer depends on whether window are fading
if (!fade_running && ev_is_active(&ps->fade_timer)) {
ev_timer_stop(EV_A_ & ps->fade_timer);
} else if (fade_running && !ev_is_active(&ps->fade_timer)) {
ev_timer_set(&ps->fade_timer, fade_timeout(ps), 0);
ev_timer_start(EV_A_ & ps->fade_timer);
}

int64_t after_preprocess_us;
clock_gettime(CLOCK_MONOTONIC, &now);
after_preprocess_us = (now.tv_sec * 1000000LL + now.tv_nsec / 1000);
Expand Down Expand Up @@ -1895,11 +1835,6 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
// false.
ps->backend_busy = (ps->frame_pacing && did_render);
ps->next_render = 0;

if (!fade_running) {
ps->fade_time = 0L;
}

ps->render_queued = false;

// TODO(yshui) Investigate how big the X critical section needs to be. There are
Expand All @@ -1909,6 +1844,8 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
// event.
if (animation) {
queue_redraw(ps);
} else {
ps->fade_time = 0L;
}
if (ps->vblank_scheduler) {
// Even if we might not want to render during next vblank, we want to keep
Expand Down
10 changes: 6 additions & 4 deletions src/render.c
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint)
const int y = w->g.y;
const uint16_t wid = to_u16_checked(w->widthb);
const uint16_t hei = to_u16_checked(w->heightb);
const double window_opacity = animatable_get(&w->opacity);
const double window_opacity = win_animatable_get(w, WIN_SCRIPT_OPACITY);

xcb_render_picture_t pict = w->paint.pict;

Expand Down Expand Up @@ -808,8 +808,10 @@ win_paint_shadow(session_t *ps, struct managed_win *w, region_t *reg_paint) {
.x = -(w->shadow_dx),
.y = -(w->shadow_dy),
};
double shadow_opacity = win_animatable_get(w, WIN_SCRIPT_SHADOW_OPACITY) *
ps->o.shadow_opacity * ps->o.frame_opacity;
render(ps, 0, 0, w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, w->shadow_width,
w->shadow_height, w->widthb, w->heightb, w->shadow_opacity, true, false, 0,
w->shadow_height, w->widthb, w->heightb, shadow_opacity, true, false, 0,
w->shadow_paint.pict, w->shadow_paint.ptex, reg_paint, NULL,
should_clip ? &clip : NULL);
if (td) {
Expand Down Expand Up @@ -901,7 +903,7 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t
auto const wid = to_u16_checked(w->widthb);
auto const hei = to_u16_checked(w->heightb);
const int cr = w ? w->corner_radius : 0;
const double window_opacity = animatable_get(&w->opacity);
const double window_opacity = win_animatable_get(w, WIN_SCRIPT_OPACITY);

double factor_center = 1.0;
// Adjust blur strength according to window opacity, to make it appear
Expand Down Expand Up @@ -1146,7 +1148,7 @@ void paint_all(session_t *ps, struct managed_win *t) {
}

// Only clip shadows above visible windows
if (animatable_get(&w->opacity) * MAX_ALPHA >= 1) {
if (win_animatable_get(w, WIN_SCRIPT_OPACITY) * MAX_ALPHA >= 1) {
if (w->clip_shadow_above) {
// Add window bounds to shadow-clip region
pixman_region32_union(&reg_shadow_clip, &reg_shadow_clip,
Expand Down
Loading

0 comments on commit 9f5da3a

Please sign in to comment.