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 <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2024-04-14 23:22:55 +01:00
parent 51ad4caa3f
commit 77898f429d
No known key found for this signature in database
GPG Key ID: D3A4405BE6CC17F4
10 changed files with 641 additions and 3 deletions

View File

@ -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 :

View File

@ -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));

View File

@ -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.

View File

@ -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 = []

View File

@ -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;

View File

@ -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,
};
}
}
/// 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);
}

View File

@ -0,0 +1,429 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#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);
}

View File

@ -0,0 +1,27 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#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);

View File

@ -3,6 +3,8 @@
#include <stddef.h>
#include <uthash.h>
#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 =

View File

@ -4,6 +4,7 @@
#include <pixman.h>
#include <stdint.h>
#include <xcb/xproto.h>
#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 {