From 60075d76574b33c96742728d2e3f979f1ef3450a Mon Sep 17 00:00:00 2001 From: Jente Hidskes Date: Sun, 20 Jan 2019 13:42:36 +0100 Subject: [PATCH] Implement damage tracking Fixes #5. --- meson.build | 7 +- output.c | 238 ++++++++++++++++++++++++++++++++++++++++++++++------ output.h | 9 +- seat.c | 10 +++ view.c | 6 ++ view.h | 1 + xdg_shell.c | 13 +++ xdg_shell.h | 1 + xwayland.c | 13 +++ xwayland.h | 1 + 10 files changed, 271 insertions(+), 28 deletions(-) diff --git a/meson.build b/meson.build index 421247d..9f9a187 100644 --- a/meson.build +++ b/meson.build @@ -23,10 +23,14 @@ if get_option('buildtype').startswith('debug') add_project_arguments('-DDEBUG', language : 'c') endif +cc = meson.get_compiler('c') + wlroots = dependency('wlroots') wayland_protos = dependency('wayland-protocols', version: '>=1.14') wayland_server = dependency('wayland-server') +pixman = dependency('pixman-1') xkbcommon = dependency('xkbcommon') +math = cc.find_library('m') wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') wayland_scanner = find_program('wayland-scanner') @@ -52,7 +56,6 @@ server_protos = declare_dependency( ) if get_option('xwayland') - cc = meson.get_compiler('c') wlroots_has_xwayland = cc.get_define('WLR_HAS_XWAYLAND', prefix: '#include ', dependencies: wlroots) == '1' if not wlroots_has_xwayland error('Cannot build Cage with XWayland support: wlroots has been built without it') @@ -100,6 +103,8 @@ executable( wayland_server, wlroots, xkbcommon, + pixman, + math, ], install: true, ) diff --git a/output.c b/output.c index 8f90dea..eba43d2 100644 --- a/output.c +++ b/output.c @@ -18,21 +18,103 @@ #include #include #include +#include #include #include #include #include +#include #include "output.h" #include "server.h" #include "view.h" +static void +scissor_output(struct wlr_output *output, pixman_box32_t *rect) +{ + struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); + + struct wlr_box box = { + .x = rect->x1, + .y = rect->y1, + .width = rect->x2 - rect->x1, + .height = rect->y2 - rect->y1, + }; + + int output_width, output_height; + wlr_output_transformed_resolution(output, &output_width, &output_height); + enum wl_output_transform transform = wlr_output_transform_invert(output->transform); + wlr_box_transform(&box, &box, transform, output_width, output_height); + + wlr_renderer_scissor(renderer, &box); +} + +static void +send_frame_done(struct wlr_surface *surface, int _unused, int __unused, void *data) +{ + struct timespec *now = data; + wlr_surface_send_frame_done(surface, now); +} + +/* Used to move all of the data necessary to damage a surface. */ +struct damage_data { + struct cg_output *output; + double x; + double y; + bool whole; +}; + +static void +damage_surface(struct wlr_surface *surface, int sx, int sy, void *data) +{ + struct damage_data *ddata = data; + struct cg_output *output = ddata->output; + struct wlr_output *wlr_output = output->wlr_output; + + if (!wlr_surface_has_buffer(surface)) { + return; + } + + double x = ddata->x + sx, y = ddata->y + sy; + wlr_output_layout_output_coords(output->server->output_layout, wlr_output, &x, &y); + + struct wlr_box box = { + .x = x * wlr_output->scale, + .y = y * wlr_output->scale, + .width = surface->current.width * wlr_output->scale, + .height = surface->current.height * wlr_output->scale, + }; + + if (pixman_region32_not_empty(&surface->buffer_damage)) { + pixman_region32_t damage; + pixman_region32_init(&damage); + wlr_surface_get_effective_damage(surface, &damage); + + wlr_region_scale(&damage, &damage, wlr_output->scale); + if (ceil(wlr_output->scale) > surface->current.scale) { + /* When scaling up a surface it'll become + blurry, so we need to expand the damage + region. */ + wlr_region_expand(&damage, &damage, + ceil(wlr_output->scale) - surface->current.scale); + } + pixman_region32_translate(&damage, box.x, box.y); + wlr_output_damage_add(output->damage, &damage); + pixman_region32_fini(&damage); + } + + if (ddata->whole) { + wlr_output_damage_add_box(output->damage, &box); + } +} + /* Used to move all of the data necessary to render a surface from the * top-level frame handler to the per-surface render function. */ struct render_data { struct wlr_output_layout *output_layout; struct wlr_output *output; struct timespec *when; + pixman_region32_t *damage; double x, y; }; @@ -52,22 +134,37 @@ render_surface(struct wlr_surface *surface, int sx, int sy, void *data) return; } - double ox = 0, oy = 0; - wlr_output_layout_output_coords(rdata->output_layout, output, &ox, &oy); - ox += rdata->x + sx, oy += rdata->y + sy; + double x = rdata->x + sx, y = rdata->y + sy; + wlr_output_layout_output_coords(rdata->output_layout, output, &x, &y); struct wlr_box box = { - .x = ox * output->scale, - .y = oy * output->scale, + .x = x * output->scale, + .y = y * output->scale, .width = surface->current.width * output->scale, .height = surface->current.height * output->scale, }; + pixman_region32_t damage; + pixman_region32_init(&damage); + pixman_region32_union_rect(&damage, &damage, box.x, box.y, box.width, box.height); + pixman_region32_intersect(&damage, &damage, rdata->damage); + if (!pixman_region32_not_empty(&damage)) { + goto damage_finish; + } + float matrix[9]; enum wl_output_transform transform = wlr_output_transform_invert(surface->current.transform); wlr_matrix_project_box(matrix, &box, transform, 0, output->transform_matrix); - wlr_render_texture_with_matrix(surface->renderer, texture, matrix, 1); - wlr_surface_send_frame_done(surface, rdata->when); + + int nrects; + pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); + for (int i = 0; i < nrects; i++) { + scissor_output(output, &rects[i]); + wlr_render_texture_with_matrix(surface->renderer, texture, matrix, 1); + } + + damage_finish: + pixman_region32_fini(&damage); } static void @@ -90,31 +187,55 @@ drag_icons_for_each_surface(struct cg_server *server, wlr_surface_iterator_func_ } static void -handle_output_frame(struct wl_listener *listener, void *data) +handle_output_damage_frame(struct wl_listener *listener, void *data) { - struct cg_output *output = wl_container_of(listener, output, frame); + struct cg_output *output = wl_container_of(listener, output, damage_frame); struct wlr_renderer *renderer = wlr_backend_get_renderer(output->server->backend); - if (!wlr_output_make_current(output->wlr_output, NULL)) { - wlr_log(WLR_DEBUG, "Cannot make damage output current"); - return; - } - struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); - int width, height; - wlr_output_transformed_resolution(output->wlr_output, &width, &height); + bool needs_swap; + pixman_region32_t damage; + pixman_region32_init(&damage); + if (!wlr_output_damage_make_current(output->damage, &needs_swap, &damage)) { + wlr_log(WLR_ERROR, "Cannot make damage output current"); + goto damage_finish; + } - wlr_renderer_begin(renderer, width, height); + if (!needs_swap) { + wlr_log(WLR_DEBUG, "Output doesn't need swap and isn't damaged"); + goto damage_finish; + } + + int output_width, output_height; + wlr_output_transformed_resolution(output->wlr_output, &output_width, &output_height); + + wlr_renderer_begin(renderer, output_width, output_height); + + if (!pixman_region32_not_empty(&damage)) { + wlr_log(WLR_DEBUG, "Output isn't damaged but needs a buffer swap"); + goto renderer_end; + } + +#ifdef DEBUG + // TODO: guard this behind a flag. + wlr_renderer_clear(renderer, (float[]){1, 0, 0, 1}); +#endif float color[4] = {0.3, 0.3, 0.3, 1.0}; - wlr_renderer_clear(renderer, color); + int nrects; + pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); + for (int i = 0; i < nrects; i++) { + scissor_output(output->wlr_output, &rects[i]); + wlr_renderer_clear(renderer, color); + } struct render_data rdata = { .output_layout = output->server->output_layout, .output = output->wlr_output, .when = &now, + .damage = &damage, }; struct cg_view *view; @@ -125,12 +246,32 @@ handle_output_frame(struct wl_listener *listener, void *data) } drag_icons_for_each_surface(output->server, render_surface, &rdata); + + renderer_end: /* Draw software cursor in case hardware cursors aren't available. This is a no-op when they are. */ - wlr_output_render_software_cursors(output->wlr_output, NULL); - + wlr_output_render_software_cursors(output->wlr_output, &damage); + wlr_renderer_scissor(renderer, NULL); wlr_renderer_end(renderer); - wlr_output_swap_buffers(output->wlr_output, NULL, NULL); + +#ifdef DEBUG + pixman_region32_union_rect(&damage, &damage, 0, 0, output_width, output_height); +#endif + + enum wl_output_transform transform = wlr_output_transform_invert(output->wlr_output->transform); + wlr_region_transform(&damage, &damage, transform, output_width, output_height); + + if (!wlr_output_damage_swap_buffers(output->damage, &now, &damage)) { + wlr_log(WLR_ERROR, "Could not swap buffers"); + } + + damage_finish: + pixman_region32_fini(&damage); + + wl_list_for_each_reverse(view, &output->server->views, link) { + view_for_each_surface(view, send_frame_done, &now); + } + drag_icons_for_each_surface(output->server, send_frame_done, &now); } static void @@ -156,15 +297,15 @@ handle_output_mode(struct wl_listener *listener, void *data) } static void -handle_output_destroy(struct wl_listener *listener, void *data) +output_destroy(struct cg_output *output) { - struct cg_output *output = wl_container_of(listener, output, destroy); struct cg_server *server = output->server; wl_list_remove(&output->destroy.link); wl_list_remove(&output->mode.link); wl_list_remove(&output->transform.link); - wl_list_remove(&output->frame.link); + wl_list_remove(&output->damage_frame.link); + wl_list_remove(&output->damage_destroy.link); free(output); server->output = NULL; @@ -173,6 +314,21 @@ handle_output_destroy(struct wl_listener *listener, void *data) wl_display_terminate(server->wl_display); } +static void +handle_output_damage_destroy(struct wl_listener *listener, void *data) +{ + struct cg_output *output = wl_container_of(listener, output, damage_destroy); + output_destroy(output); +} + +static void +handle_output_destroy(struct wl_listener *listener, void *data) +{ + struct cg_output *output = wl_container_of(listener, output, destroy); + wlr_output_damage_destroy(output->damage); + output_destroy(output); +} + void handle_new_output(struct wl_listener *listener, void *data) { @@ -191,15 +347,18 @@ handle_new_output(struct wl_listener *listener, void *data) server->output = calloc(1, sizeof(struct cg_output)); server->output->wlr_output = wlr_output; server->output->server = server; + server->output->damage = wlr_output_damage_create(wlr_output); - server->output->frame.notify = handle_output_frame; - wl_signal_add(&wlr_output->events.frame, &server->output->frame); server->output->mode.notify = handle_output_mode; wl_signal_add(&wlr_output->events.mode, &server->output->mode); server->output->transform.notify = handle_output_transform; wl_signal_add(&wlr_output->events.transform, &server->output->transform); server->output->destroy.notify = handle_output_destroy; wl_signal_add(&wlr_output->events.destroy, &server->output->destroy); + server->output->damage_frame.notify = handle_output_damage_frame; + wl_signal_add(&server->output->damage->events.frame, &server->output->damage_frame); + server->output->damage_destroy.notify = handle_output_damage_destroy; + wl_signal_add(&server->output->damage->events.destroy, &server->output->damage_destroy); wlr_output_layout_add_auto(server->output_layout, wlr_output); @@ -214,4 +373,31 @@ handle_new_output(struct wl_listener *listener, void *data) /* Place the cursor in the center of the screen. */ wlr_cursor_warp(server->seat->cursor, NULL, wlr_output->width / 2, wlr_output->height / 2); + wlr_output_damage_add_whole(server->output->damage); +} + +void +output_damage_view_surface(struct cg_output *cg_output, struct cg_view *view) +{ + struct damage_data data = { + .output = cg_output, + .x = view->x, + .y = view->y, + .whole = false, + }; + view_for_each_surface(view, damage_surface, &data); +} + +void +output_damage_drag_icon(struct cg_output *cg_output, struct cg_drag_icon *drag_icon) +{ + struct damage_data data = { + .output = cg_output, + .x = drag_icon->x, + .y = drag_icon->y, + .whole = true, + }; + wlr_surface_for_each_surface(drag_icon->wlr_drag_icon->surface, + damage_surface, + &data); } diff --git a/output.h b/output.h index 511cad7..645d6b9 100644 --- a/output.h +++ b/output.h @@ -3,19 +3,26 @@ #include #include +#include +#include "seat.h" #include "server.h" +#include "view.h" struct cg_output { struct cg_server *server; struct wlr_output *wlr_output; + struct wlr_output_damage *damage; - struct wl_listener frame; struct wl_listener mode; struct wl_listener transform; struct wl_listener destroy; + struct wl_listener damage_frame; + struct wl_listener damage_destroy; }; void handle_new_output(struct wl_listener *listener, void *data); +void output_damage_view_surface(struct cg_output *output, struct cg_view *view); +void output_damage_drag_icon(struct cg_output *output, struct cg_drag_icon *icon); #endif diff --git a/seat.c b/seat.c index c91fb84..aab3bbf 100644 --- a/seat.c +++ b/seat.c @@ -528,12 +528,20 @@ handle_cursor_motion(struct wl_listener *listener, void *data) wlr_idle_notify_activity(seat->server->idle, seat->seat); } +static void +drag_icon_damage(struct cg_drag_icon *drag_icon) +{ + output_damage_drag_icon(drag_icon->seat->server->output, drag_icon); +} + static void drag_icon_update_position(struct cg_drag_icon *drag_icon) { struct wlr_drag_icon *wlr_icon = drag_icon->wlr_drag_icon; struct cg_seat *seat = drag_icon->seat; + drag_icon_damage(drag_icon); + if (wlr_icon->is_pointer) { drag_icon->x = seat->cursor->x; drag_icon->y = seat->cursor->y; @@ -546,6 +554,8 @@ drag_icon_update_position(struct cg_drag_icon *drag_icon) drag_icon->x = seat->touch_x; drag_icon->y = seat->touch_y; } + + drag_icon_damage(drag_icon); } static void diff --git a/view.c b/view.c index e0efc1d..47f39f4 100644 --- a/view.c +++ b/view.c @@ -45,6 +45,12 @@ view_is_transient_for(struct cg_view *child, struct cg_view *parent) { return child->impl->is_transient_for(child, parent); } +void +view_damage_surface(struct cg_view *view) +{ + output_damage_view_surface(view->server->output, view); +} + void view_activate(struct cg_view *view, bool activate) { diff --git a/view.h b/view.h index 364afc2..4185f20 100644 --- a/view.h +++ b/view.h @@ -48,6 +48,7 @@ struct cg_view_impl { char *view_get_title(struct cg_view *view); bool view_is_primary(struct cg_view *view); bool view_is_transient_for(struct cg_view *child, struct cg_view *parent); +void view_damage_surface(struct cg_view *view); void view_activate(struct cg_view *view, bool activate); void view_position(struct cg_view *view); void view_for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data); diff --git a/xdg_shell.c b/xdg_shell.c index b2fdc9c..e7432bc 100644 --- a/xdg_shell.c +++ b/xdg_shell.c @@ -105,12 +105,22 @@ wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double return wlr_xdg_surface_surface_at(xdg_shell_view->xdg_surface, sx, sy, sub_x, sub_y); } +static void +handle_xdg_shell_surface_commit(struct wl_listener *listener, void *data) +{ + struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, commit); + struct cg_view *view = &xdg_shell_view->view; + view_damage_surface(view); +} + static void handle_xdg_shell_surface_unmap(struct wl_listener *listener, void *data) { struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, unmap); struct cg_view *view = &xdg_shell_view->view; + wl_list_remove(&xdg_shell_view->commit.link); + view_unmap(view); } @@ -120,6 +130,9 @@ handle_xdg_shell_surface_map(struct wl_listener *listener, void *data) struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, map); struct cg_view *view = &xdg_shell_view->view; + xdg_shell_view->commit.notify = handle_xdg_shell_surface_commit; + wl_signal_add(&xdg_shell_view->xdg_surface->surface->events.commit, &xdg_shell_view->commit); + view_map(view, xdg_shell_view->xdg_surface->surface); } diff --git a/xdg_shell.h b/xdg_shell.h index 89ec5f3..7214aae 100644 --- a/xdg_shell.h +++ b/xdg_shell.h @@ -13,6 +13,7 @@ struct cg_xdg_shell_view { struct wl_listener destroy; struct wl_listener unmap; struct wl_listener map; + struct wl_listener commit; // TODO: allow applications to go to fullscreen from maximized? // struct wl_listener request_fullscreen; }; diff --git a/xwayland.c b/xwayland.c index 64327e0..4ad789a 100644 --- a/xwayland.c +++ b/xwayland.c @@ -107,12 +107,22 @@ wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double return wlr_surface_surface_at(view->wlr_surface, sx, sy, sub_x, sub_y); } +static void +handle_xwayland_surface_commit(struct wl_listener *listener, void *data) +{ + struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, commit); + struct cg_view *view = &xwayland_view->view; + view_damage_surface(view); +} + static void handle_xwayland_surface_unmap(struct wl_listener *listener, void *data) { struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, unmap); struct cg_view *view = &xwayland_view->view; + wl_list_remove(&xwayland_view->commit.link); + view_unmap(view); } @@ -127,6 +137,9 @@ handle_xwayland_surface_map(struct wl_listener *listener, void *data) view->y = xwayland_view->xwayland_surface->y; } + xwayland_view->commit.notify = handle_xwayland_surface_commit; + wl_signal_add(&xwayland_view->xwayland_surface->surface->events.commit, &xwayland_view->commit); + xwayland_view->ever_been_mapped = true; view_map(view, xwayland_view->xwayland_surface->surface); } diff --git a/xwayland.h b/xwayland.h index f31aa8c..24ad4e4 100644 --- a/xwayland.h +++ b/xwayland.h @@ -27,6 +27,7 @@ struct cg_xwayland_view { struct wl_listener destroy; struct wl_listener unmap; struct wl_listener map; + struct wl_listener commit; // TODO: allow applications to go to fullscreen from maximized? // struct wl_listener request_fullscreen; };