diff --git a/include/wlr/types/wlr_damage_ring.h b/include/wlr/types/wlr_damage_ring.h new file mode 100644 index 00000000..9415a556 --- /dev/null +++ b/include/wlr/types/wlr_damage_ring.h @@ -0,0 +1,81 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_DAMAGE_RING_H +#define WLR_TYPES_WLR_DAMAGE_RING_H + +#include + +/* For triple buffering, a history of two frames is required. */ +#define WLR_DAMAGE_RING_PREVIOUS_LEN 2 + +struct wlr_box; + +struct wlr_damage_ring { + int32_t width, height; + + // Difference between the current buffer and the previous one + pixman_region32_t current; + + // private state + + pixman_region32_t previous[WLR_DAMAGE_RING_PREVIOUS_LEN]; + size_t previous_idx; +}; + +void wlr_damage_ring_init(struct wlr_damage_ring *ring); + +void wlr_damage_ring_finish(struct wlr_damage_ring *ring); + +/** + * Set ring bounds and damage the ring fully. + * + * Next time damage will be added, it will be cropped to the ring bounds. + * If at least one of the dimensions is 0, bounds are removed. + * + * By default, a damage ring doesn't have bounds. + */ +void wlr_damage_ring_set_bounds(struct wlr_damage_ring *ring, + int32_t width, int32_t height); + +/** + * Add a region to the current damage. + * + * Returns true if the region intersects the ring bounds, false otherwise. + */ +bool wlr_damage_ring_add(struct wlr_damage_ring *ring, + pixman_region32_t *damage); + +/** + * Add a box to the current damage. + * + * Returns true if the box intersects the ring bounds, false otherwise. + */ +bool wlr_damage_ring_add_box(struct wlr_damage_ring *ring, + const struct wlr_box *box); + +/** + * Damage the ring fully. + */ +void wlr_damage_ring_add_whole(struct wlr_damage_ring *ring); + +/** + * Rotate the damage ring. This needs to be called after using the accumulated + * damage, e.g. after rendering to an output's back buffer. + */ +void wlr_damage_ring_rotate(struct wlr_damage_ring *ring); + +/** + * Get accumulated damage, which is the difference between the current buffer + * and the buffer with age of buffer_age; in context of rendering, this is + * the region that needs to be redrawn. + */ +void wlr_damage_ring_get_buffer_damage(struct wlr_damage_ring *ring, + int buffer_age, pixman_region32_t *damage); + +#endif diff --git a/types/meson.build b/types/meson.build index 16870b83..53bf852b 100644 --- a/types/meson.build +++ b/types/meson.build @@ -30,6 +30,7 @@ wlr_files += files( 'wlr_buffer.c', 'wlr_compositor.c', 'wlr_cursor.c', + 'wlr_damage_ring.c', 'wlr_data_control_v1.c', 'wlr_drm.c', 'wlr_export_dmabuf_v1.c', diff --git a/types/wlr_damage_ring.c b/types/wlr_damage_ring.c new file mode 100644 index 00000000..378051c7 --- /dev/null +++ b/types/wlr_damage_ring.c @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include +#include "util/signal.h" + +#define WLR_DAMAGE_RING_MAX_RECTS 20 + +void wlr_damage_ring_init(struct wlr_damage_ring *ring) { + memset(ring, 0, sizeof(*ring)); + + ring->width = INT_MAX; + ring->height = INT_MAX; + + pixman_region32_init(&ring->current); + for (size_t i = 0; i < WLR_DAMAGE_RING_PREVIOUS_LEN; ++i) { + pixman_region32_init(&ring->previous[i]); + } +} + +void wlr_damage_ring_finish(struct wlr_damage_ring *ring) { + pixman_region32_fini(&ring->current); + for (size_t i = 0; i < WLR_DAMAGE_RING_PREVIOUS_LEN; ++i) { + pixman_region32_fini(&ring->previous[i]); + } +} + +void wlr_damage_ring_set_bounds(struct wlr_damage_ring *ring, + int32_t width, int32_t height) { + if (width == 0 || height == 0) { + ring->width = INT_MAX; + ring->height = INT_MAX; + } else { + ring->width = width; + ring->height = height; + } + wlr_damage_ring_add_whole(ring); +} + +bool wlr_damage_ring_add(struct wlr_damage_ring *ring, + pixman_region32_t *damage) { + pixman_region32_t clipped; + pixman_region32_init(&clipped); + pixman_region32_intersect_rect(&clipped, damage, + 0, 0, ring->width, ring->height); + bool intersects = pixman_region32_not_empty(&clipped); + if (intersects) { + pixman_region32_union(&ring->current, &ring->current, &clipped); + } + pixman_region32_fini(&clipped); + return intersects; +} + +bool wlr_damage_ring_add_box(struct wlr_damage_ring *ring, + const struct wlr_box *box) { + struct wlr_box clipped = { + .x = 0, + .y = 0, + .width = ring->width, + .height = ring->height, + }; + if (wlr_box_intersection(&clipped, &clipped, box)) { + pixman_region32_union_rect(&ring->current, + &ring->current, clipped.x, clipped.y, + clipped.width, clipped.height); + return true; + } + return false; +} + +void wlr_damage_ring_add_whole(struct wlr_damage_ring *ring) { + pixman_region32_union_rect(&ring->current, + &ring->current, 0, 0, ring->width, ring->height); +} + +void wlr_damage_ring_rotate(struct wlr_damage_ring *ring) { + // modular decrement + ring->previous_idx = ring->previous_idx + + WLR_DAMAGE_RING_PREVIOUS_LEN - 1; + ring->previous_idx %= WLR_DAMAGE_RING_PREVIOUS_LEN; + + pixman_region32_copy(&ring->previous[ring->previous_idx], &ring->current); + pixman_region32_clear(&ring->current); +} + +void wlr_damage_ring_get_buffer_damage(struct wlr_damage_ring *ring, + int buffer_age, pixman_region32_t *damage) { + if (buffer_age <= 0 || buffer_age - 1 > WLR_DAMAGE_RING_PREVIOUS_LEN) { + pixman_region32_clear(damage); + pixman_region32_union_rect(damage, damage, + 0, 0, ring->width, ring->height); + } else { + pixman_region32_copy(damage, &ring->current); + + // Accumulate damage from old buffers + for (int i = 0; i < buffer_age - 1; ++i) { + int j = (ring->previous_idx + i) % WLR_DAMAGE_RING_PREVIOUS_LEN; + pixman_region32_union(damage, damage, &ring->previous[j]); + } + + // Check the number of rectangles + int n_rects = pixman_region32_n_rects(damage); + if (n_rects > WLR_DAMAGE_RING_MAX_RECTS) { + pixman_box32_t *extents = pixman_region32_extents(damage); + pixman_region32_union_rect(damage, damage, + extents->x1, extents->y1, + extents->x2 - extents->x1, + extents->y2 - extents->y1); + } + } +}