render: re-write cell clipping to use pixman destination clipping

Our home rolled clip-to-cell code was, obviously, not correct.

The original problem was that we couldn't use pixman clipping since we
have multiple threads writing to the same pixman image, and thus there
would be races between the threads setting clipping.

The fix is actually simple - just instantiate one pixman
image (referencing the same backing image data) for each rendering
thread.
This commit is contained in:
Daniel Eklöf 2020-06-04 15:39:19 +02:00
parent 0d0df56796
commit 8b320ed296
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
3 changed files with 62 additions and 60 deletions

View file

@ -437,36 +437,25 @@ render_cell(struct terminal *term, pixman_image_t *pix,
pixman_image_t *clr_pix = pixman_image_create_solid_fill(&fg);
if (glyph != NULL) {
/*
* Clip to cell.
*
* What we'd really like to do here is use normal destination
* clipping on the pixman image. Unfortunately, that's not
* possible since we have multiple threads accessing the same
* pixman image.
*
* Instead, manually adjust the destination offsets and
* width/heights.
*/
int glyph_x = max(x + glyph->x, x);
int mask_x = max(0, -glyph->x);
int glyph_w = min(glyph->width, glyph->cols * term->cell_width);
int glyph_y = max(y + font_baseline(term) - glyph->y, y);
int mask_y = max(0, -font_baseline(term) + glyph->y);
int glyph_h = min(glyph->height, term->cell_height);
/* Clip to cell */
pixman_region32_t clip;
pixman_region32_init_rect(
&clip, x, y, glyph->cols * term->cell_width, term->cell_height);
pixman_image_set_clip_region32(pix, &clip);
if (unlikely(pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8)) {
/* Glyph surface is a pre-rendered image (typically a color emoji...) */
if (!(cell->attrs.blink && term->blink.state == BLINK_OFF)) {
pixman_image_composite32(
PIXMAN_OP_OVER, glyph->pix, NULL, pix, 0, 0, 0, 0,
glyph_x, glyph_y, glyph_w, glyph_h);
x + glyph->x, y + font_baseline(term) - glyph->y,
glyph->width, glyph->height);
}
} else {
pixman_image_composite32(
PIXMAN_OP_OVER, clr_pix, glyph->pix, pix, 0, 0,
mask_x, mask_y, glyph_x, glyph_y, glyph_w, glyph_h);
PIXMAN_OP_OVER, clr_pix, glyph->pix, pix, 0, 0, 0, 0,
x + glyph->x, y + font_baseline(term) - glyph->y,
glyph->width, glyph->height);
/* Combining characters */
if (composed != NULL) {
@ -477,8 +466,6 @@ render_cell(struct terminal *term, pixman_image_t *pix,
if (g == NULL)
continue;
/* TODO: clip to cell */
pixman_image_composite32(
PIXMAN_OP_OVER, clr_pix, g->pix, pix, 0, 0, 0, 0,
/* Some fonts use a negative offset, while others use a
@ -489,6 +476,8 @@ render_cell(struct terminal *term, pixman_image_t *pix,
}
}
}
pixman_image_set_clip_region32(pix, NULL);
}
pixman_image_unref(clr_pix);
@ -528,7 +517,7 @@ render_margin(struct terminal *term, struct buffer *buf, int start_line, int end
if (top) {
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix, &bg, 1,
PIXMAN_OP_SRC, buf->pix[0], &bg, 1,
&(pixman_rectangle16_t){0, 0, term->width, term->margins.top});
wl_surface_damage_buffer(
term->window->surface, 0, 0, term->width, term->margins.top);
@ -536,14 +525,14 @@ render_margin(struct terminal *term, struct buffer *buf, int start_line, int end
if (bottom) {
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix, &bg, 1,
PIXMAN_OP_SRC, buf->pix[0], &bg, 1,
&(pixman_rectangle16_t){0, bmargin, term->width, term->margins.bottom});
wl_surface_damage_buffer(
term->window->surface, 0, bmargin, term->width, term->margins.bottom);
}
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix, &bg, 2,
PIXMAN_OP_SRC, buf->pix[0], &bg, 2,
(pixman_rectangle16_t[]){
/* Left */
{0, term->margins.top + start_line * term->cell_height,
@ -851,7 +840,7 @@ render_worker_thread(void *_ctx)
switch (row_no) {
default:
assert(buf != NULL);
render_row(term, buf->pix, grid_row_in_view(term->grid, row_no), row_no);
render_row(term, buf->pix[my_id], grid_row_in_view(term->grid, row_no), row_no);
break;
case -1:
@ -930,7 +919,7 @@ render_csd_part(struct terminal *term,
pixman_image_t *src = pixman_image_create_solid_fill(color);
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix, color, 1,
PIXMAN_OP_SRC, buf->pix[0], color, 1,
&(pixman_rectangle16_t){0, 0, buf->width, buf->height});
pixman_image_unref(src);
}
@ -947,7 +936,7 @@ render_csd_title(struct terminal *term)
unsigned long cookie = shm_cookie_csd(term, CSD_SURF_TITLE);
struct buffer *buf = shm_get_buffer(
term->wl->shm, info.width, info.height, cookie, false);
term->wl->shm, info.width, info.height, cookie, false, 1);
uint32_t _color = term->colors.default_fg;
uint16_t alpha = 0xffff;
@ -978,7 +967,7 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx)
unsigned long cookie = shm_cookie_csd(term, surf_idx);
struct buffer *buf = shm_get_buffer(
term->wl->shm, info.width, info.height, cookie, false);
term->wl->shm, info.width, info.height, cookie, false, 1);
pixman_color_t color = color_hex_to_pixman_with_alpha(0, 0);
render_csd_part(term, surf, buf, info.width, info.height, &color);
@ -1024,7 +1013,7 @@ render_csd_button_minimize(struct terminal *term, struct buffer *buf)
};
pixman_composite_triangles(
PIXMAN_OP_OVER, src, buf->pix, PIXMAN_a1,
PIXMAN_OP_OVER, src, buf->pix[0], PIXMAN_a1,
0, 0, 0, 0, 1, &tri);
pixman_image_unref(src);
}
@ -1046,7 +1035,7 @@ render_csd_button_maximize_maximized(
const int y_margin = (buf->height - width) / 2;
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix, &color, 4,
PIXMAN_OP_SRC, buf->pix[0], &color, 4,
(pixman_rectangle16_t[]){
{x_margin, y_margin, width, thick},
{x_margin, y_margin + thick, thick, width - 2 * thick},
@ -1097,7 +1086,7 @@ render_csd_button_maximize_window(
};
pixman_composite_triangles(
PIXMAN_OP_OVER, src, buf->pix, PIXMAN_a1,
PIXMAN_OP_OVER, src, buf->pix[0], PIXMAN_a1,
0, 0, 0, 0, 1, &tri);
pixman_image_unref(src);
@ -1127,7 +1116,7 @@ render_csd_button_close(struct terminal *term, struct buffer *buf)
const int y_margin = (buf->height - width) / 2;
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix, &color, 1,
PIXMAN_OP_SRC, buf->pix[0], &color, 1,
&(pixman_rectangle16_t){x_margin, y_margin, width, width});
pixman_image_unref(src);
@ -1147,7 +1136,7 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx)
unsigned long cookie = shm_cookie_csd(term, surf_idx);
struct buffer *buf = shm_get_buffer(
term->wl->shm, info.width, info.height, cookie, false);
term->wl->shm, info.width, info.height, cookie, false, 1);
uint32_t _color;
uint16_t alpha = 0xffff;
@ -1274,7 +1263,7 @@ grid_render(struct terminal *term)
unsigned long cookie = shm_cookie_grid(term);
struct buffer *buf = shm_get_buffer(
term->wl->shm, term->width, term->height, cookie, true);
term->wl->shm, term->width, term->height, cookie, true, 1 + term->render.workers.count);
/* If we resized the window, or is flashing, or just stopped flashing */
if (term->render.last_buf != buf ||
@ -1324,7 +1313,7 @@ grid_render(struct terminal *term)
/* If cell is already dirty, it will be rendered anyway */
if (cell->attrs.clean) {
cell->attrs.clean = 0;
int cols = render_cell(term, buf->pix, row, at.col, at.row, false);
int cols = render_cell(term, buf->pix[0], row, at.col, at.row, false);
wl_surface_damage_buffer(
term->window->surface,
@ -1416,7 +1405,7 @@ grid_render(struct terminal *term)
if (!row->dirty)
continue;
render_row(term, buf->pix, row, r);
render_row(term, buf->pix[0], row, r);
row->dirty = false;
wl_surface_damage_buffer(
@ -1470,7 +1459,7 @@ grid_render(struct terminal *term)
cell->attrs.clean = 0;
term->render.last_cursor.row = row;
int cols_updated = render_cell(
term, buf->pix, row, term->grid->cursor.point.col, view_aligned_row, true);
term, buf->pix[0], row, term->grid->cursor.point.col, view_aligned_row, true);
wl_surface_damage_buffer(
term->window->surface,
@ -1479,13 +1468,13 @@ grid_render(struct terminal *term)
cols_updated * term->cell_width, term->cell_height);
}
render_sixel_images(term, buf->pix);
render_sixel_images(term, buf->pix[0]);
if (term->flash.active) {
/* Note: alpha is pre-computed in each color component */
/* TODO: dim while searching */
pixman_image_fill_rectangles(
PIXMAN_OP_OVER, buf->pix,
PIXMAN_OP_OVER, buf->pix[0],
&(pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff},
1, &(pixman_rectangle16_t){0, 0, term->width, term->height});
@ -1568,7 +1557,7 @@ render_search_box(struct terminal *term)
size_t glyph_offset = term->render.search_glyph_offset;
unsigned long cookie = shm_cookie_search(term);
struct buffer *buf = shm_get_buffer(term->wl->shm, width, height, cookie, false);
struct buffer *buf = shm_get_buffer(term->wl->shm, width, height, cookie, false, 1);
/* Background - yellow on empty/match, red on mismatch */
pixman_color_t color = color_hex_to_pixman(
@ -1576,12 +1565,12 @@ render_search_box(struct terminal *term)
? term->colors.table[3] : term->colors.table[1]);
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix, &color,
PIXMAN_OP_SRC, buf->pix[0], &color,
1, &(pixman_rectangle16_t){width - visible_width, 0, visible_width, height});
pixman_color_t transparent = color_hex_to_pixman_with_alpha(0, 0);
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix, &transparent,
PIXMAN_OP_SRC, buf->pix[0], &transparent,
1, &(pixman_rectangle16_t){0, 0, width - visible_width, height});
struct fcft_font *font = term->fonts[0];
@ -1608,7 +1597,7 @@ render_search_box(struct terminal *term)
i++)
{
if (i == term->search.cursor)
draw_bar(term, buf->pix, font, &fg, x, y);
draw_bar(term, buf->pix[0], font, &fg, x, y);
const struct fcft_glyph *glyph = fcft_glyph_rasterize(
font, term->search.buf[i], true);
@ -1618,7 +1607,7 @@ render_search_box(struct terminal *term)
pixman_image_t *src = pixman_image_create_solid_fill(&fg);
pixman_image_composite32(
PIXMAN_OP_OVER, src, glyph->pix, buf->pix, 0, 0, 0, 0,
PIXMAN_OP_OVER, src, glyph->pix, buf->pix[0], 0, 0, 0, 0,
x + glyph->x, y + font_baseline(term) - glyph->y,
glyph->width, glyph->height);
pixman_image_unref(src);
@ -1627,7 +1616,7 @@ render_search_box(struct terminal *term)
}
if (term->search.cursor >= term->search.len)
draw_bar(term, buf->pix, font, &fg, x, y);
draw_bar(term, buf->pix[0], font, &fg, x, y);
quirk_weston_subsurface_desync_on(term->window->search_sub_surface);

34
shm.c
View file

@ -67,11 +67,15 @@ shm_set_max_pool_size(off_t _max_pool_size)
static void
buffer_destroy_dont_close(struct buffer *buf)
{
if (buf->pix != NULL)
pixman_image_unref(buf->pix);
if (buf->pix != NULL) {
for (size_t i = 0; i < buf->pix_instances; i++)
if (buf->pix[i] != NULL)
pixman_image_unref(buf->pix[i]);
}
if (buf->wl_buf != NULL)
wl_buffer_destroy(buf->wl_buf);
free(buf->pix);
buf->pix = NULL;
buf->wl_buf = NULL;
buf->mmapped = NULL;
@ -145,7 +149,7 @@ instantiate_offset(struct wl_shm *shm, struct buffer *buf, off_t new_offset)
void *mmapped = MAP_FAILED;
struct wl_buffer *wl_buf = NULL;
pixman_image_t *pix = NULL;
pixman_image_t **pix = calloc(buf->pix_instances, sizeof(*pix));
mmapped = (uint8_t *)buf->real_mmapped + new_offset;
@ -157,11 +161,13 @@ instantiate_offset(struct wl_shm *shm, struct buffer *buf, off_t new_offset)
}
/* One pixman image for each worker thread (do we really need multiple?) */
pix = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8, buf->width, buf->height, (uint32_t *)mmapped, buf->stride);
if (pix == NULL) {
LOG_ERR("failed to create pixman image");
goto err;
for (size_t i = 0; i < buf->pix_instances; i++) {
pix[i] = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8, buf->width, buf->height, (uint32_t *)mmapped, buf->stride);
if (pix[i] == NULL) {
LOG_ERR("failed to create pixman image");
goto err;
}
}
buf->offset = new_offset;
@ -173,8 +179,12 @@ instantiate_offset(struct wl_shm *shm, struct buffer *buf, off_t new_offset)
return true;
err:
if (pix != NULL)
pixman_image_unref(pix);
if (pix != NULL) {
for (size_t i = 0; i < buf->pix_instances; i++)
if (pix[i] != NULL)
pixman_image_unref(pix[i]);
}
free(pix);
if (wl_buf != NULL)
wl_buffer_destroy(wl_buf);
@ -183,7 +193,7 @@ err:
}
struct buffer *
shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie, bool scrollable)
shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie, bool scrollable, size_t pix_instances)
{
/* Purge buffers marked for purging */
tll_foreach(buffers, it) {
@ -216,6 +226,7 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie,
cookie, &it->item);
it->item.busy = true;
it->item.purge = false;
assert(it->item.pix_instances == pix_instances);
return &it->item;
}
}
@ -333,6 +344,7 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie,
.stride = stride,
.busy = true,
.size = size,
.pix_instances = pix_instances,
.fd = pool_fd,
.pool = pool,
.scrollable = scrollable,

5
shm.h
View file

@ -19,7 +19,8 @@ struct buffer {
void *mmapped; /* Raw data (TODO: rename) */
struct wl_buffer *wl_buf;
pixman_image_t *pix;
pixman_image_t **pix;
size_t pix_instances;
/* Internal */
int fd; /* memfd */
@ -34,7 +35,7 @@ struct buffer {
};
struct buffer *shm_get_buffer(
struct wl_shm *shm, int width, int height, unsigned long cookie, bool scrollable);
struct wl_shm *shm, int width, int height, unsigned long cookie, bool scrollable, size_t pix_instances);
void shm_fini(void);
void shm_set_max_pool_size(off_t max_pool_size);