From 77898f429d0548da9ac29585c331fc36f061de73 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 14 Apr 2024 23:22:55 +0100 Subject: [PATCH] Add command builder The command builder generates a list of backend operations needed to render a layout. The goal is two fold: 1. Help with backend interface transition. The command builder generates calls of the new interface, with it we will be able to remove the compat layer. 2. Reduce code duplication. Several places in the code need to know how windows are rendered. We need that info to compare past and current layouts to figure out the overall damage region; we need that info to figure out which part of each render command is visible in the final result, and clip out unnecessary regions; and finally we need that info to actually render. I don't want to repeat the same code three times. So instead we generate all the render steps at once as commands, then operate on those commands. Right now this function is called and the results logged, otherwise it's not used. Signed-off-by: Yuxuan Shui --- src/backend/backend.c | 73 ++++++ src/backend/backend.h | 45 ++++ src/common.h | 2 + src/meson.build | 2 +- src/picom.c | 5 + src/region.h | 42 +++- src/renderer/command_builder.c | 429 +++++++++++++++++++++++++++++++++ src/renderer/command_builder.h | 27 +++ src/renderer/layout.c | 4 + src/renderer/layout.h | 15 +- 10 files changed, 641 insertions(+), 3 deletions(-) create mode 100644 src/renderer/command_builder.c create mode 100644 src/renderer/command_builder.h diff --git a/src/backend/backend.c b/src/backend/backend.c index 8347180b..7c6b4da4 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -10,6 +10,7 @@ #include "config.h" #include "log.h" #include "region.h" +#include "renderer/command_builder.h" #include "renderer/layout.h" #include "transition.h" #include "types.h" @@ -106,6 +107,28 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) { layout_manager_append_layout( ps->layout_manager, ps->wm, (struct geometry){.width = ps->root_width, .height = ps->root_height}); + auto layout = layout_manager_layout(ps->layout_manager); + command_builder_build( + ps->command_builder, layout, ps->o.force_win_blend, + ps->o.blur_background_frame, ps->o.inactive_dim_fixed, ps->o.max_brightness, + ps->o.inactive_dim, &ps->shadow_exclude_reg, + ps->o.crop_shadow_to_monitor ? &ps->monitors : NULL, ps->o.wintype_option); + { + auto layer = layout->layers - 1; + auto layer_end = &layout->commands[layout->first_layer_start]; + auto end = &layout->commands[layout->number_of_commands]; + log_trace("Desktop background"); + for (auto i = layout->commands; i != end; i++) { + if (i == layer_end) { + layer += 1; + layer_end += layer->number_of_commands; + log_trace("Layer for window %#010x @ %#010x (%s)", + layer->win->base.id, layer->win->client_win, + layer->win->name); + } + log_backend_command(TRACE, *i); + } + } // 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 @@ -546,4 +569,54 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) { return true; } +static inline const char *render_command_source_name(enum backend_command_source source) { + switch (source) { + case BACKEND_COMMAND_SOURCE_WINDOW: return "window"; + case BACKEND_COMMAND_SOURCE_SHADOW: return "shadow"; + case BACKEND_COMMAND_SOURCE_BACKGROUND: return "background"; + } + unreachable(); +} + +void log_backend_command_(enum log_level level, const char *func, + const struct backend_command *cmd) { + if (level < log_get_level_tls()) { + return; + } + + log_printf(tls_logger, level, func, "Render command: %p", cmd); + switch (cmd->op) { + case BACKEND_COMMAND_BLIT: + log_printf(tls_logger, level, func, "blit %s%s", + render_command_source_name(cmd->source), + cmd->need_mask_image ? ", with mask image" : ""); + log_printf(tls_logger, level, func, "origin: %d,%d", cmd->origin.x, + cmd->origin.y); + log_printf(tls_logger, level, func, "mask region:"); + log_region_(level, func, &cmd->blit.mask->region); + log_printf(tls_logger, level, func, "opaque region:"); + log_region_(level, func, &cmd->opaque_region); + break; + case BACKEND_COMMAND_COPY_AREA: + log_printf(tls_logger, level, func, "copy area from %s", + render_command_source_name(cmd->source)); + log_printf(tls_logger, level, func, "origin: %d,%d", cmd->origin.x, + cmd->origin.y); + log_printf(tls_logger, level, func, "region:"); + log_region_(level, func, cmd->copy_area.region); + break; + case BACKEND_COMMAND_BLUR: + log_printf(tls_logger, level, func, "blur%s", + cmd->need_mask_image ? ", with mask image" : ""); + log_printf(tls_logger, level, func, "origin: %d,%d", cmd->origin.x, + cmd->origin.y); + log_printf(tls_logger, level, func, "mask region:"); + log_region_(level, func, &cmd->blur.mask->region); + break; + case BACKEND_COMMAND_INVALID: + log_printf(tls_logger, level, func, "invalid"); + break; + } +} + // vim: set noet sw=8 ts=8 : diff --git a/src/backend/backend.h b/src/backend/backend.h index 74b90d03..c9c2f6f2 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -195,6 +195,47 @@ enum backend_image_capability { BACKEND_IMAGE_CAP_DST = 1 << 1, }; +enum backend_command_op { + BACKEND_COMMAND_INVALID = -1, + BACKEND_COMMAND_BLIT, + BACKEND_COMMAND_BLUR, + BACKEND_COMMAND_COPY_AREA, +}; + +/// Symbolic references used as render command source images. The actual `image_handle` +/// will later be filled in by the renderer using this symbolic reference. +enum backend_command_source { + BACKEND_COMMAND_SOURCE_WINDOW, + BACKEND_COMMAND_SOURCE_SHADOW, + BACKEND_COMMAND_SOURCE_BACKGROUND, +}; + +// TODO(yshui) might need better names + +struct backend_command { + enum backend_command_op op; + struct coord origin; + enum backend_command_source source; + union { + struct { + struct backend_blit_args blit; + /// Region of the screen that will be covered by this blit + /// operations, in screen coordinates. + region_t opaque_region; + }; + struct { + image_handle source_image; + const region_t *region; + } copy_area; + struct backend_blur_args blur; + }; + /// Mask used for the operation. Note `copy_area` command uses this to store its + /// `region` argument. + struct backend_mask mask; + /// Whether renderer should fill in `mask.image`. + bool need_mask_image; +}; + enum backend_quirk { /// Backend cannot do blur quickly. The compositor will avoid using blur to create /// shadows on this backend @@ -633,3 +674,7 @@ extern struct backend_operations *backend_list[]; /// Returns if any render command is issued. IOW if nothing on the screen has changed, /// this function will return false. bool paint_all_new(session_t *ps, struct managed_win *t) attr_nonnull(1); +void log_backend_command_(enum log_level level, const char *func, + const struct backend_command *cmd); +#define log_backend_command(level, cmd) \ + log_backend_command_(LOG_LEVEL_##level, __func__, &(cmd)); diff --git a/src/common.h b/src/common.h index 46257987..463c9be0 100644 --- a/src/common.h +++ b/src/common.h @@ -267,6 +267,8 @@ typedef struct session { // TODO(yshui) move render related fields into separate struct /// Render planner struct layout_manager *layout_manager; + /// Render command builder + struct command_builder *command_builder; /// Whether the root image has been changed since last render bool root_damaged; /// Whether all windows are currently redirected. diff --git a/src/meson.build b/src/meson.build index 417efb72..3dffc3d2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -10,7 +10,7 @@ base_deps = [ srcs = [ files('picom.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', 'utils.c', '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') ] + 'vblank.c', 'transition.c', 'wm.c', 'renderer/layout.c', 'renderer/command_builder.c') ] picom_inc = include_directories('.') cflags = [] diff --git a/src/picom.c b/src/picom.c index 72566566..05f74732 100644 --- a/src/picom.c +++ b/src/picom.c @@ -66,6 +66,7 @@ #include "options.h" #include "region.h" #include "render.h" +#include "renderer/command_builder.h" #include "renderer/layout.h" #include "statistics.h" #include "types.h" @@ -2554,6 +2555,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy, log_debug("%#010x", w->id); } + ps->command_builder = command_builder_new(); + ps->pending_updates = true; write_pid(ps); @@ -2580,6 +2583,8 @@ static void session_destroy(session_t *ps) { if (ps->redirected) { unredirect(ps); } + command_builder_free(ps->command_builder); + ps->command_builder = NULL; file_watch_destroy(ps->loop, ps->file_watch_handle); ps->file_watch_handle = NULL; diff --git a/src/region.h b/src/region.h index 041fb745..54ad4a4b 100644 --- a/src/region.h +++ b/src/region.h @@ -30,6 +30,31 @@ static inline void dump_region(const region_t *x) { } } +static inline void log_region_(enum log_level level, const char *func, const region_t *x) { + if (level < log_get_level_tls()) { + return; + } + + int nrects; + const rect_t *rects = pixman_region32_rectangles((region_t *)x, &nrects); + if (nrects == 0) { + log_printf(tls_logger, level, func, "\t(empty)"); + return; + } + for (int i = 0; i < min2(nrects, 3); i++) { + log_printf(tls_logger, level, func, "\t(%d, %d) - (%d, %d)", rects[i].x1, + rects[i].y1, rects[i].x2, rects[i].y2); + } + if (nrects > 3) { + auto extent = pixman_region32_extents(x); + log_printf(tls_logger, level, func, "\t..."); + log_printf(tls_logger, level, func, "\ttotal: (%d, %d) - (%d, %d)", + extent->x1, extent->y1, extent->x2, extent->y2); + } +} + +#define log_region(level, x) log_region_(LOG_LEVEL_##level, __func__, x) + /// Convert one xcb rectangle to our rectangle type static inline rect_t from_x_rect(const xcb_rectangle_t *rect) { return (rect_t){ @@ -106,4 +131,19 @@ static inline rect_t region_translate_rect(rect_t rect, struct coord origin) { .x2 = rect.x2 + origin.x, .y2 = rect.y2 + origin.y, }; -} \ No newline at end of file +} + +/// Subtract `other`, placed at `origin`, from `region`. +static inline void +region_subtract(region_t *region, struct coord origin, const region_t *other) { + pixman_region32_translate(region, -origin.x, -origin.y); + pixman_region32_subtract(region, region, other); + pixman_region32_translate(region, origin.x, origin.y); +} + +/// Union `region` with `other` placed at `origin`. +static inline void region_union(region_t *region, struct coord origin, const region_t *other) { + pixman_region32_translate(region, -origin.x, -origin.y); + pixman_region32_union(region, region, other); + pixman_region32_translate(region, origin.x, origin.y); +} diff --git a/src/renderer/command_builder.c b/src/renderer/command_builder.c new file mode 100644 index 00000000..86640c16 --- /dev/null +++ b/src/renderer/command_builder.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +#include "command_builder.h" + +#include "common.h" +#include "layout.h" +#include "win.h" + +/// Generate commands for rendering the body of the window in `layer`. +/// +/// @param[in] frame_region frame region of the window, in window local coordinates +/// @param[out] cmd output commands, when multiple commands are generated, +/// it's stored in `cmd` going backwards, i.e. cmd - 1, -2, ... +/// @return number of commands generated +static inline unsigned +commands_for_window_body(struct layer *layer, struct backend_command *cmd, + const region_t *frame_region, bool inactive_dim_fixed, + double inactive_dim, double max_brightness) { + auto w = layer->win; + auto mode = win_calc_mode(layer->win); + int border_width = w->g.border_width; + double dim = 0; + if (w->dim) { + dim = inactive_dim; + if (!inactive_dim_fixed) { + dim *= layer->opacity; + } + } + 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); + } + cmd->op = BACKEND_COMMAND_BLIT; + cmd->source = BACKEND_COMMAND_SOURCE_WINDOW; + cmd->origin = layer->origin; + cmd->blit = (struct backend_blit_args){ + .border_width = border_width, + .mask = &cmd->mask, + .corner_radius = w->corner_radius, + .opacity = layer->opacity, + .dim = dim, + .ewidth = w->widthb, + .eheight = w->heightb, + .shader = w->fg_shader ? w->fg_shader->backend_shader : NULL, + .color_inverted = w->invert_color, + .max_brightness = max_brightness}; + cmd->mask.inverted = false; + cmd->mask.corner_radius = 0; + cmd->mask.origin = (struct coord){}; + pixman_region32_copy(&cmd->mask.region, &w->bounding_shape); + if (w->frame_opacity < 1) { + pixman_region32_subtract(&cmd->mask.region, &cmd->mask.region, frame_region); + } + cmd->need_mask_image = false; + pixman_region32_init(&cmd->opaque_region); + if (mode == WMODE_SOLID || mode == WMODE_FRAME_TRANS) { + pixman_region32_copy(&cmd->opaque_region, &cmd->mask.region); + } + if (mode == WMODE_FRAME_TRANS) { + pixman_region32_subtract(&cmd->opaque_region, &cmd->opaque_region, frame_region); + } + if (w->corner_radius > 0) { + win_region_remove_corners(w, &cmd->opaque_region); + } + pixman_region32_translate(&cmd->opaque_region, layer->origin.x, layer->origin.y); + if (w->frame_opacity == 1 || w->frame_opacity == 0) { + return 1; + } + + cmd -= 1; + cmd->op = BACKEND_COMMAND_BLIT; + cmd->origin = layer->origin; + cmd->source = BACKEND_COMMAND_SOURCE_WINDOW; + cmd->need_mask_image = false; + cmd->blit = cmd[1].blit; + cmd->blit.mask = &cmd->mask; + cmd->blit.opacity *= w->frame_opacity; + cmd->mask.origin = (struct coord){}; + cmd->mask.inverted = false; + cmd->mask.corner_radius = 0; + pixman_region32_copy(&cmd->mask.region, frame_region); + pixman_region32_init(&cmd->opaque_region); + return 2; +} + +/// Generate render command for the shadow in `layer` +/// +/// @param[in] end the end of the commands generated for this `layer`. +static inline unsigned +command_for_shadow(struct layer *layer, struct backend_command *cmd, + const struct win_option *wintype_options, const region_t *shadow_exclude, + const struct x_monitors *monitors, const struct backend_command *end) { + auto w = layer->win; + if (!w->shadow) { + return 0; + } + cmd->op = BACKEND_COMMAND_BLIT; + cmd->origin = layer->shadow_origin; + cmd->source = BACKEND_COMMAND_SOURCE_SHADOW; + // Initialize mask region in the current window's coordinates, we + // will later move it to the correct coordinates + pixman_region32_clear(&cmd->mask.region); + pixman_region32_union_rect( + &cmd->mask.region, &cmd->mask.region, layer->shadow_origin.x - layer->origin.x, + layer->shadow_origin.y - layer->origin.y, (unsigned)layer->shadow_size.width, + (unsigned)layer->shadow_size.height); + log_trace("Calculate shadow for %#010x (%s)", w->base.id, w->name); + log_region(TRACE, &cmd->mask.region); + if (!wintype_options[w->window_type].full_shadow) { + // We need to not draw under the window + // From this command up, until the next WINDOW_START + // should be blits for the current window. + for (auto j = cmd + 1; j != end; j++) { + assert(j->op == BACKEND_COMMAND_BLIT); + assert(j->source == BACKEND_COMMAND_SOURCE_WINDOW); + assert(j->mask.origin.x == 0 && j->mask.origin.y == 0); + if (j->blit.corner_radius == 0) { + pixman_region32_subtract( + &cmd->mask.region, &cmd->mask.region, &j->mask.region); + } else { + region_t mask_without_corners; + pixman_region32_init(&mask_without_corners); + pixman_region32_copy(&mask_without_corners, &j->mask.region); + win_region_remove_corners(layer->win, &mask_without_corners); + pixman_region32_subtract(&cmd->mask.region, &cmd->mask.region, + &mask_without_corners); + pixman_region32_fini(&mask_without_corners); + } + } + } + log_region(TRACE, &cmd->mask.region); + // Move mask region to screen coordinates for shadow exclusion + // calculation + pixman_region32_translate(&cmd->mask.region, layer->origin.x, layer->origin.y); + if (shadow_exclude) { + pixman_region32_subtract(&cmd->mask.region, &cmd->mask.region, shadow_exclude); + } + if (monitors && w->randr_monitor >= 0 && w->randr_monitor < monitors->count) { + pixman_region32_intersect(&cmd->mask.region, &cmd->mask.region, + &monitors->regions[w->randr_monitor]); + } + log_region(TRACE, &cmd->mask.region); + // Finally move mask region to the correct coordinates + pixman_region32_translate(&cmd->mask.region, -layer->shadow_origin.x, + -layer->shadow_origin.y); + cmd->mask.corner_radius = w->corner_radius; + cmd->mask.inverted = true; + cmd->mask.origin = (struct coord){}; + cmd->need_mask_image = w->corner_radius > 0; + if (cmd->need_mask_image) { + // If we use the window's mask image, we need to align the + // mask region's origin with it. + cmd->mask.origin = + (struct coord){.x = layer->origin.x - layer->shadow_origin.x, + .y = layer->origin.y - layer->shadow_origin.y}; + pixman_region32_translate(&cmd->mask.region, -cmd->mask.origin.x, + -cmd->mask.origin.y); + } + log_region(TRACE, &cmd->mask.region); + cmd->blit = (struct backend_blit_args){ + .opacity = layer->opacity, + .max_brightness = 1, + .mask = &cmd->mask, + .ewidth = layer->shadow_size.width, + .eheight = layer->shadow_size.height, + }; + pixman_region32_init(&cmd->opaque_region); + return 1; +} + +static inline unsigned +command_for_blur(struct layer *layer, struct backend_command *cmd, + const region_t *frame_region, bool force_blend, bool blur_frame) { + auto w = layer->win; + auto mode = win_calc_mode(w); + if (!w->blur_background || layer->blur_opacity == 0) { + return 0; + } + cmd->op = BACKEND_COMMAND_BLUR; + cmd->origin = (struct coord){}; + cmd->blur.opacity = layer->blur_opacity; + cmd->blur.mask = &cmd->mask; + cmd->mask.origin = (struct coord){.x = layer->origin.x, .y = layer->origin.y}; + cmd->need_mask_image = w->corner_radius > 0; + cmd->mask.corner_radius = w->corner_radius; + cmd->mask.inverted = false; + if (force_blend || mode == WMODE_TRANS) { + pixman_region32_copy(&cmd->mask.region, &w->bounding_shape); + } else if (blur_frame && mode == WMODE_FRAME_TRANS) { + pixman_region32_copy(&cmd->mask.region, frame_region); + } else { + return 0; + } + return 1; +} + +static inline void +command_builder_apply_transparent_clipping(struct layout *layout, region_t *scratch_region) { + // Going from top down, apply transparent-clipping + if (layout->len == 0) { + return; + } + + pixman_region32_clear(scratch_region); + auto end = &layout->commands[layout->number_of_commands - 1]; + auto begin = &layout->commands[layout->first_layer_start - 1]; + auto layer = &layout->layers[layout->len - 1]; + // `layer_start` is one before the first command for this layer + auto layer_start = end - layer->number_of_commands; + for (auto i = end; i != begin; i--) { + if (i == layer_start) { + if (layer->win->transparent_clipping) { + auto win = layer->win; + auto mode = win_calc_mode(layer->win); + region_t tmp; + pixman_region32_init(&tmp); + if (mode == WMODE_TRANS) { + pixman_region32_copy(&tmp, &win->bounding_shape); + } else if (mode == WMODE_FRAME_TRANS) { + win_get_region_frame_local(win, &tmp); + } + pixman_region32_translate(&tmp, layer->origin.x, + layer->origin.y); + pixman_region32_union(scratch_region, scratch_region, &tmp); + pixman_region32_fini(&tmp); + } + layer -= 1; + layer_start -= layer->number_of_commands; + } + + if (i->op == BACKEND_COMMAND_BLUR || + (i->op == BACKEND_COMMAND_BLIT && + i->source != BACKEND_COMMAND_SOURCE_BACKGROUND)) { + struct coord scratch_origin = { + .x = -i->origin.x - i->mask.origin.x, + .y = -i->origin.y - i->mask.origin.y, + }; + region_subtract(&i->mask.region, scratch_origin, scratch_region); + } + if (i->op == BACKEND_COMMAND_BLIT && + i->source != BACKEND_COMMAND_SOURCE_BACKGROUND) { + pixman_region32_subtract(&i->opaque_region, &i->opaque_region, + scratch_region); + } + } +} +static inline void +command_builder_apply_shadow_clipping(struct layout *layout, region_t *scratch_region) { + // Going from bottom up, apply clipping-shadow-above + pixman_region32_clear(scratch_region); + auto begin = &layout->commands[layout->first_layer_start]; + auto end = &layout->commands[layout->number_of_commands]; + auto layer = layout->layers - 1; + // `layer_end` is one after the last command for this layer + auto layer_end = begin; + bool clip_shadow_above = false; + for (auto i = begin; i != end; i++) { + if (i == layer_end) { + layer += 1; + layer_end += layer->number_of_commands; + clip_shadow_above = layer->win->clip_shadow_above; + } + + struct coord mask_origin = { + .x = i->mask.origin.x + i->origin.x, + .y = i->mask.origin.y + i->origin.y, + }; + if (i->op == BACKEND_COMMAND_BLUR) { + region_subtract(scratch_region, mask_origin, &i->mask.region); + } else if (i->op == BACKEND_COMMAND_BLIT) { + if (i->source == BACKEND_COMMAND_SOURCE_SHADOW) { + mask_origin.x = -mask_origin.x; + mask_origin.y = -mask_origin.y; + region_subtract(&i->mask.region, mask_origin, scratch_region); + } else if (i->source == BACKEND_COMMAND_SOURCE_WINDOW && + clip_shadow_above) { + region_union(scratch_region, mask_origin, &i->mask.region); + } + } + } +} + +struct command_builder { + region_t scratch_region; + struct list_node free_command_lists; +}; + +struct command_list { + struct list_node free_list; + unsigned capacity; + struct command_builder *super; + struct backend_command commands[]; +}; + +static struct command_list * +command_builder_command_list_new(struct command_builder *cb, unsigned ncmds) { + const auto size = sizeof(struct command_list) + sizeof(struct backend_command[ncmds]); + struct command_list *list = NULL; + unsigned capacity = 0; + if (!list_is_empty(&cb->free_command_lists)) { + list = list_entry(cb->free_command_lists.next, struct command_list, free_list); + capacity = list->capacity; + list_remove(&list->free_list); + } + if (capacity < ncmds || capacity / 2 > ncmds) { + for (unsigned i = ncmds; i < capacity; i++) { + pixman_region32_fini(&list->commands[i].mask.region); + } + + struct command_list *new_list = realloc(list, size); + allocchk(new_list); + list = new_list; + list_init_head(&list->free_list); + list->capacity = ncmds; + list->super = cb; + + for (unsigned i = capacity; i < ncmds; i++) { + list->commands[i].op = BACKEND_COMMAND_INVALID; + pixman_region32_init(&list->commands[i].mask.region); + } + } + return list; +} + +void command_builder_command_list_free(struct backend_command *cmds) { + if (!cmds) { + return; + } + + auto list = container_of(cmds, struct command_list, commands[0]); + for (unsigned i = 0; i < list->capacity; i++) { + auto cmd = &list->commands[i]; + if (cmd->op == BACKEND_COMMAND_BLIT) { + pixman_region32_fini(&cmd->opaque_region); + } + cmd->op = BACKEND_COMMAND_INVALID; + } + list_insert_after(&list->super->free_command_lists, &list->free_list); +} + +struct command_builder *command_builder_new(void) { + auto cb = ccalloc(1, struct command_builder); + pixman_region32_init(&cb->scratch_region); + list_init_head(&cb->free_command_lists); + return cb; +} + +void command_builder_free(struct command_builder *cb) { + list_foreach_safe(struct command_list, i, &cb->free_command_lists, free_list) { + list_remove(&i->free_list); + for (unsigned j = 0; j < i->capacity; j++) { + pixman_region32_fini(&i->commands[j].mask.region); + } + free(i); + } + + pixman_region32_fini(&cb->scratch_region); + free(cb); +} + +// TODO(yshui) reduce the number of parameters by storing the final effective parameter +// value in `struct managed_win`. +void command_builder_build(struct command_builder *cb, struct layout *layout, + 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) { + + unsigned ncmds = 1; + for (unsigned i = 0; i < layout->len; i++) { + auto layer = &layout->layers[i]; + auto mode = win_calc_mode(layer->win); + if (layer->win->blur_background && layer->blur_opacity > 0 && + (force_blend || mode == WMODE_TRANS || + (blur_frame && mode == WMODE_FRAME_TRANS))) { + // Needs blur + ncmds += 1; + } + if (layer->win->shadow) { + ncmds += 1; + } + if (layer->win->frame_opacity < 1 && layer->win->frame_opacity > 0) { + // Needs to draw the frame separately + ncmds += 1; + } + ncmds += 1; // window body + } + + auto list = command_builder_command_list_new(cb, ncmds); + layout->commands = list->commands; + + auto cmd = &layout->commands[ncmds - 1]; + for (int i = to_int_checked(layout->len) - 1; i >= 0; i--) { + auto layer = &layout->layers[i]; + auto frame_region = win_get_region_frame_local_by_val(layer->win); + auto last = cmd; + + // Add window body + cmd -= commands_for_window_body(layer, cmd, &frame_region, inactive_dim_fixed, + inactive_dim, max_brightness); + + // Add shadow + cmd -= command_for_shadow(layer, cmd, wintype_options, shadow_exclude, + monitors, last + 1); + + // Add blur + cmd -= command_for_blur(layer, cmd, &frame_region, force_blend, blur_frame); + + layer->number_of_commands = (unsigned)(last - cmd); + } + + // Command for the desktop background + cmd->op = BACKEND_COMMAND_COPY_AREA; + cmd->source = BACKEND_COMMAND_SOURCE_BACKGROUND; + cmd->origin = (struct coord){}; + pixman_region32_reset( + &cmd->mask.region, + (rect_t[]){{.x1 = 0, .y1 = 0, .x2 = layout->size.width, .y2 = layout->size.height}}); + cmd->copy_area.region = &cmd->mask.region; + assert(cmd == list->commands); + + layout->first_layer_start = 1; + layout->number_of_commands = ncmds; + + command_builder_apply_transparent_clipping(layout, &cb->scratch_region); + command_builder_apply_shadow_clipping(layout, &cb->scratch_region); +} diff --git a/src/renderer/command_builder.h b/src/renderer/command_builder.h new file mode 100644 index 00000000..f94a5eef --- /dev/null +++ b/src/renderer/command_builder.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui +#pragma once + +#include "backend/backend.h" +#include "types.h" + +struct command_builder; +struct layout; + +struct command_builder *command_builder_new(void); +void command_builder_free(struct command_builder *); + +void command_builder_command_list_free(struct backend_command *cmds); + +/// Generate render commands that need to be executed to render the current layout. +/// This function updates `layout->commands` with the list of generated commands, and also +/// the `number_of_commands` field of each of the layers in `layout`. The list of +/// commands must later be freed with `command_builder_command_list_free` +/// It is guaranteed that each of the command's region of operation (e.g. the mask.region +/// argument of blit), will be store in `struct backend_command::mask`. This might not +/// stay true after further passes. +void command_builder_build(struct command_builder *cb, struct layout *layout, + 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); diff --git a/src/renderer/layout.c b/src/renderer/layout.c index b5288b89..6faaecc3 100644 --- a/src/renderer/layout.c +++ b/src/renderer/layout.c @@ -3,6 +3,8 @@ #include #include +#include "command_builder.h" +#include "common.h" #include "list.h" #include "region.h" #include "transition.h" @@ -98,6 +100,7 @@ static void layout_deinit(struct layout *layout) { pixman_region32_fini(&layout->layers[i].damaged); } free(layout->layers); + command_builder_command_list_free(layout->commands); *layout = (struct layout){}; } @@ -153,6 +156,7 @@ void layout_manager_append_layout(struct layout_manager *lm, struct wm *wm, auto prev_layout = &lm->layouts[lm->current]; lm->current = (lm->current + 1) % lm->max_buffer_age; auto layout = &lm->layouts[lm->current]; + command_builder_command_list_free(layout->commands); unsigned nlayers = wm_stack_num_managed_windows(wm); if (nlayers > layout->capacity) { struct layer *new_layers = diff --git a/src/renderer/layout.h b/src/renderer/layout.h index 716863a9..900d33f0 100644 --- a/src/renderer/layout.h +++ b/src/renderer/layout.h @@ -4,6 +4,7 @@ #include #include #include +#include "backend/backend.h" #include "region.h" #include "types.h" @@ -41,6 +42,9 @@ struct layer { /// Opacity of the background blur of this window float blur_opacity; + /// How many commands are needed to render this layer + unsigned number_of_commands; + /// Rank of this layer in the previous frame, -1 if this window /// appears in this frame for the first time int prev_rank; @@ -71,8 +75,17 @@ struct layout { unsigned len; /// Capacity of `layers` unsigned capacity; - /// Layers as a flat array + /// Layers as a flat array, from bottom to top in stack order. struct layer *layers; + /// Number of commands in `commands` + unsigned number_of_commands; + /// Where does the commands for the bottom most layer start. + /// Any commands before that is for the desktop background. + unsigned first_layer_start; + /// Commands that are needed to render this layout. Commands + /// are recorded in the same order as the layers they correspond to. Each layer + /// can have 0 or more commands associated with it. + struct backend_command *commands; }; struct render_plan {