mirror of
https://github.com/tmux/tmux.git
synced 2026-09-20 20:08:09 +00:00
sixel: coalesce vertically adjacent rows into one SIXEL write
The redraw loop hands images to the sixel backend one grid line at a time, so a placement N rows tall arrives as N separate one-row rectangles - each a full scale/encode pass and its own DCS sequence carrying its own copy of the palette. Hold a run of vertically adjacent rows back in struct sixel_output and write it as a single SIXEL once the run ends (image_draw_flush, called from the redraw loop after each pane/line pass). Also make image_tty_update() report whether the backend actually changed, and skip the client redraw in tty_update_features() when it didn't. That function runs on every DA/secondary DA/extended DA answer, most of which just confirm what's already known; the redraw it triggers repaints from tmux's grid and discards anything a pane wrote via DCS passthrough that tmux doesn't model - visibly flashing images away right after they're drawn. Ported from paranoidi's fork (commit 98cc26d) onto the damage-rectangle based redraw introduced on this branch; the three image_draw_flush() call sites are placed at the equivalent point in redraw_draw_pane_lines(), redraw_draw_lines(), and redraw_draw_damage_rect(). Verified against tmux/tmux#5445. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -109,11 +109,27 @@ struct sixel_image_cache {
|
||||
struct sixel_image_cache *next;
|
||||
};
|
||||
|
||||
/*
|
||||
* Contiguous rows of one placement, held back so that they can be written as
|
||||
* a single SIXEL instead of one per row. See sixel_draw_rect.
|
||||
*/
|
||||
struct sixel_pending {
|
||||
struct image *image;
|
||||
u_int source_x;
|
||||
u_int source_y;
|
||||
u_int width;
|
||||
u_int height;
|
||||
u_int destination_x;
|
||||
u_int destination_y;
|
||||
};
|
||||
|
||||
struct sixel_output {
|
||||
/* Per-terminal cached images and aggregate cache state. */
|
||||
struct sixel_image_cache *images;
|
||||
size_t size;
|
||||
uint64_t age;
|
||||
/* Rows waiting to be written as one SIXEL. */
|
||||
struct sixel_pending pending;
|
||||
};
|
||||
|
||||
struct sixel_hgram {
|
||||
@@ -1457,6 +1473,8 @@ sixel_free_output(struct tty *tty, __unused int send)
|
||||
|
||||
if (so == NULL)
|
||||
return;
|
||||
/* The run is dropped, not written: the geometry it was measured at is gone. */
|
||||
so->pending.image = NULL;
|
||||
for (cache = so->images; cache != NULL; cache = next) {
|
||||
next = cache->next;
|
||||
sixel_free(cache->si);
|
||||
@@ -1548,24 +1566,26 @@ sixel_image_is_cached(struct tty *tty, struct sixel_image *si)
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Draw an image rectangle with SIXEL output. */
|
||||
/* Write the rows held in the pending run as a single SIXEL. */
|
||||
void
|
||||
sixel_draw_rect(struct tty *tty, const struct image_rect *rectangle,
|
||||
__unused const struct tty_style_ctx *style_ctx)
|
||||
sixel_flush_output(struct tty *tty)
|
||||
{
|
||||
struct sixel_output *so = tty->image_data;
|
||||
struct sixel_pending *sp;
|
||||
struct sixel_image *si, *crop;
|
||||
char *data;
|
||||
size_t size;
|
||||
u_int source_x, source_y, width, height;
|
||||
u_int destination_x, destination_y;
|
||||
|
||||
si = sixel_get_image(tty, image_rect_get_image(rectangle));
|
||||
if (so == NULL || so->pending.image == NULL)
|
||||
return;
|
||||
sp = &so->pending;
|
||||
|
||||
si = sixel_get_image(tty, sp->image);
|
||||
sp->image = NULL;
|
||||
if (si == NULL)
|
||||
return;
|
||||
image_rect_get_coords(rectangle, &source_x, &source_y, &width,
|
||||
&height, &destination_x, &destination_y);
|
||||
crop = sixel_scale(si, tty->xpixel, tty->ypixel,
|
||||
source_x, source_y, width, height, 1);
|
||||
crop = sixel_scale(si, tty->xpixel, tty->ypixel, sp->source_x,
|
||||
sp->source_y, sp->width, sp->height, 1);
|
||||
if (!sixel_image_is_cached(tty, si))
|
||||
sixel_free(si);
|
||||
if (crop == NULL)
|
||||
@@ -1576,7 +1596,7 @@ sixel_draw_rect(struct tty *tty, const struct image_rect *rectangle,
|
||||
return;
|
||||
tty_region_off(tty);
|
||||
tty_margin_off(tty);
|
||||
tty_cursor(tty, destination_x, destination_y);
|
||||
tty_cursor(tty, sp->destination_x, sp->destination_y);
|
||||
tty->flags |= TTY_NOBLOCK;
|
||||
tty_putn(tty, data, size, 0);
|
||||
/* SIXEL moves the cursor, but does not change terminal attributes. */
|
||||
@@ -1584,6 +1604,54 @@ sixel_draw_rect(struct tty *tty, const struct image_rect *rectangle,
|
||||
free(data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Queue an image rectangle for SIXEL output.
|
||||
*
|
||||
* The redraw loop hands images to the backend one grid line at a time, so a
|
||||
* placement 24 rows tall arrives as 24 separate one-row rectangles. Writing
|
||||
* each of them straight out means 24 scale and encode passes and 24 DCS
|
||||
* sequences carrying 24 copies of the palette, for what the application sent
|
||||
* as one image - and terminals have to allocate and composite each one. The
|
||||
* rows of a placement arrive in order, so hold a run of vertically adjacent
|
||||
* rows back and write them as one SIXEL when the run ends.
|
||||
*
|
||||
* Anything that is not a continuation of the run flushes it first, and
|
||||
* image_draw_flush at the end of the redraw flushes whatever is left, so no
|
||||
* other terminal output can be reordered across a pending run.
|
||||
*/
|
||||
void
|
||||
sixel_draw_rect(struct tty *tty, const struct image_rect *rectangle,
|
||||
__unused const struct tty_style_ctx *style_ctx)
|
||||
{
|
||||
struct sixel_output *so = sixel_get_output(tty);
|
||||
struct sixel_pending *sp = &so->pending;
|
||||
struct image *im = image_rect_get_image(rectangle);
|
||||
u_int source_x, source_y, width, height;
|
||||
u_int destination_x, destination_y;
|
||||
|
||||
image_rect_get_coords(rectangle, &source_x, &source_y, &width,
|
||||
&height, &destination_x, &destination_y);
|
||||
|
||||
if (sp->image == im &&
|
||||
sp->source_x == source_x &&
|
||||
sp->width == width &&
|
||||
sp->destination_x == destination_x &&
|
||||
sp->source_y + sp->height == source_y &&
|
||||
sp->destination_y + sp->height == destination_y) {
|
||||
sp->height += height;
|
||||
return;
|
||||
}
|
||||
|
||||
sixel_flush_output(tty);
|
||||
sp->image = im;
|
||||
sp->source_x = source_x;
|
||||
sp->source_y = source_y;
|
||||
sp->width = width;
|
||||
sp->height = height;
|
||||
sp->destination_x = destination_x;
|
||||
sp->destination_y = destination_y;
|
||||
}
|
||||
|
||||
/* Remove old SIXEL pixels before replaying a dirty image area. */
|
||||
void
|
||||
sixel_redraw_start(struct tty *tty, u_int x, u_int y, u_int sx, u_int sy)
|
||||
|
||||
20
image.c
20
image.c
@@ -120,15 +120,19 @@ image_tty_find_backend(struct tty *tty)
|
||||
return (&image_backend_fallback);
|
||||
}
|
||||
|
||||
/* Update a terminal's image backend after its capabilities change. */
|
||||
void
|
||||
/*
|
||||
* Update a terminal's image backend after its capabilities change. Returns 1
|
||||
* if the backend changed (so the caller knows a redraw is actually needed),
|
||||
* 0 if not.
|
||||
*/
|
||||
int
|
||||
image_tty_update(struct tty *tty)
|
||||
{
|
||||
const struct image_backend *backend;
|
||||
|
||||
backend = image_tty_find_backend(tty);
|
||||
if (tty->image_backend == backend)
|
||||
return;
|
||||
return (0);
|
||||
|
||||
if (tty->image_backend != NULL && tty->image_backend->free != NULL)
|
||||
tty->image_backend->free(tty, !!(tty->flags & TTY_OPENED));
|
||||
@@ -136,6 +140,7 @@ image_tty_update(struct tty *tty)
|
||||
tty->image_backend = backend;
|
||||
log_debug("%s: %s image backend is %s", __func__,
|
||||
tty->client->name, backend->name);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Remove Kitty placements which will be replaced by a redraw. */
|
||||
@@ -150,6 +155,15 @@ image_redraw_start(struct tty *tty, u_int x, u_int y, u_int width,
|
||||
sixel_redraw_start(tty, x, y, width, height);
|
||||
}
|
||||
|
||||
/* Write out any image output the backend is still holding back. */
|
||||
void
|
||||
image_draw_flush(struct tty *tty)
|
||||
{
|
||||
image_tty_update(tty);
|
||||
if (tty->image_backend == &image_backend_sixel)
|
||||
sixel_flush_output(tty);
|
||||
}
|
||||
|
||||
/* Return the flags for a terminal's image backend. */
|
||||
int
|
||||
image_backend_flags(struct tty *tty)
|
||||
|
||||
@@ -1719,6 +1719,9 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp,
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_draw_flush(&scene->c->tty);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1783,6 +1786,9 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
|
||||
phase);
|
||||
}
|
||||
}
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_draw_flush(&scene->c->tty);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2329,6 +2335,9 @@ redraw_draw_damage_rect(struct redraw_draw_ctx *dctx, u_int x, u_int y,
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_draw_flush(&scene->c->tty);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* SIXEL image output may disturb status cells; compose them last. */
|
||||
|
||||
4
tmux.h
4
tmux.h
@@ -4348,8 +4348,9 @@ void image_redraw_area(struct screen_write_ctx *, u_int, u_int,
|
||||
void image_redraw_all(struct screen_write_ctx *);
|
||||
void image_redraw_scroll(struct screen_write_ctx *, u_int);
|
||||
void image_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
|
||||
void image_draw_flush(struct tty *);
|
||||
int image_backend_flags(struct tty *);
|
||||
void image_tty_update(struct tty *);
|
||||
int image_tty_update(struct tty *);
|
||||
void image_tty_geometry_changed(struct tty *);
|
||||
void image_tty_free(struct tty *, int);
|
||||
void image_draw_line(struct tty *, struct screen *, u_int, u_int,
|
||||
@@ -4412,6 +4413,7 @@ void sixel_draw_rect(struct tty *,
|
||||
const struct image_rect *, const struct tty_style_ctx *);
|
||||
void sixel_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
|
||||
void sixel_free_output(struct tty *, int);
|
||||
void sixel_flush_output(struct tty *);
|
||||
struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int,
|
||||
u_int);
|
||||
void sixel_free(struct sixel_image *);
|
||||
|
||||
23
tty.c
23
tty.c
@@ -569,11 +569,15 @@ void
|
||||
tty_update_features(struct tty *tty)
|
||||
{
|
||||
struct client *c = tty->client;
|
||||
int changed = 0;
|
||||
|
||||
if (tty_apply_features(tty->term))
|
||||
if (tty_apply_features(tty->term)) {
|
||||
tty_term_apply_overrides(tty->term);
|
||||
changed = 1;
|
||||
}
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_tty_update(tty);
|
||||
if (image_tty_update(tty))
|
||||
changed = 1;
|
||||
#endif
|
||||
|
||||
if (tty_use_margin(tty))
|
||||
@@ -588,7 +592,22 @@ tty_update_features(struct tty *tty)
|
||||
/*
|
||||
* Features might have changed since the first draw during attach. For
|
||||
* example, this happens when DA responses are received.
|
||||
*
|
||||
* Only redraw when something actually did change. This function is
|
||||
* called for every DA, secondary DA and extended DA answer, and from
|
||||
* the start timer when none arrive - answers which usually just
|
||||
* confirm what is already known, either from a previous answer or
|
||||
* from terminal-features in the configuration. The redraw is not
|
||||
* free: it repaints the pane from tmux's own grid, which discards
|
||||
* anything the pane put on the terminal that tmux does not model,
|
||||
* notably an image written through DCS passthrough. That makes an
|
||||
* unnecessary redraw here visible to the user as an image that
|
||||
* appears and then vanishes a moment later, once per client, with no
|
||||
* way for the application to detect it and redraw.
|
||||
*/
|
||||
if (!changed)
|
||||
return;
|
||||
|
||||
server_redraw_client(c);
|
||||
|
||||
tty_invalidate(tty);
|
||||
|
||||
Reference in New Issue
Block a user