diff --git a/image-kitty.c b/image-kitty.c index 5c91cb7a5..5e1853b7a 100644 --- a/image-kitty.c +++ b/image-kitty.c @@ -237,17 +237,12 @@ kitty_redraw_keep(struct tty *tty, struct kitty_image_cache *entry, } /* - * Mark Kitty placements intersecting a redraw area as stale, without - * deleting them yet - see kitty_redraw_finish(). Deleting immediately here - * would, for an image whose only placements are in this area, leave it - * with none at all until the replacement is placed - some Kitty - * implementations free an image's pixel data once it has no placements - * left, which would leave the replacement referencing already-discarded - * data and render as nothing. - * - * A placement only partly inside the area is deleted as a whole, but the - * redraw only replaces the part inside, so the parts outside are placed - * again first - otherwise they would vanish from cells nothing redraws. + * Mark placements intersecting a redraw area as stale rather than deleting + * them yet (see kitty_redraw_finish()) - some implementations free an + * image's pixel data once its last placement is gone, so an image only + * placed here would go blank before its replacement lands. A placement only + * partly inside the area is deleted as a whole, so place its outside parts + * again first or they would vanish from cells nothing redraws. */ void kitty_redraw_start(struct tty *tty, u_int x, u_int y, u_int width, diff --git a/image-sixel.c b/image-sixel.c index 9d0921506..e37153566 100644 --- a/image-sixel.c +++ b/image-sixel.c @@ -1605,19 +1605,12 @@ sixel_flush_output(struct tty *tty) } /* - * 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. + * Queue an image rectangle for SIXEL output. The redraw loop hands images to + * the backend one grid line at a time, so hold back a run of vertically + * adjacent rows and write them as one SIXEL instead of one per row. + * Anything that is not a continuation flushes the run first, and + * image_draw_flush() flushes what's left at the end of the redraw, so no + * other terminal output is reordered across a pending run. */ void sixel_draw_rect(struct tty *tty, const struct image_rect *rectangle, diff --git a/image.c b/image.c index 5e487d3d8..92f52ecf8 100644 --- a/image.c +++ b/image.c @@ -1563,26 +1563,10 @@ image_extend_row(struct image_line *line, struct image_placement *placement, } /* - * Extend existing image placements to reveal more of their original width - * after a pane has grown wider. - * - * image_write() (below) only creates spans for as much of an image as fit - * in the pane at the time it was placed - the rest of the image's pixels - * are still retained (struct image is immutable and kept for as long as - * any placement references it), but nothing ever revisits that clipping - * decision, so a pane that was too narrow when an image was displayed - * stays clipped forever, even after growing wide enough to fit the rest. - * Unlike height, which recovers via ordinary scrollback (image_write() - * scrolls rather than clips when a placement is taller than the pane), - * there is no equivalent "scroll right" - this is the only way the extra - * width is ever recovered. - * - * For every grid row with image spans, this finds each distinct placement - * referenced there, works out how far its spans already reach (source_x + - * width) and its origin column (a span's x - source_x, which is the same - * for every span of the same placement), and adds spans for any newly - * revealed columns up to whichever is smaller: the image's own full width - * or the new pane width. + * Extend existing image placements to reveal more of an image after a pane + * grows wider. image_write() only creates spans for as much as fit at the + * time - unlike height, there is no "scroll right" to recover a clipped + * width later, so this is the only way the rest is ever shown. */ void image_grid_resize_width(struct grid *gd, u_int new_sx) @@ -1673,16 +1657,7 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg, } else if (screen_size_y(s) - cy <= sy) { lines = sy - (screen_size_y(s) - cy) + 1; - /* - * screen_write_scrollup() clamps its own lines argument to - * at most one scroll region height per call, so a single - * call cannot push more than that much history however - * large lines is - call it repeatedly to actually push the - * full amount, so the history-row loop below (which is - * itself bounded against gd->hsize, so it is safe even if - * this loop's assumptions are ever wrong) has real rows to - * use instead of just whatever fit in one call. - */ + /* screen_write_scrollup() clamps lines per call, so loop it. */ region_height = s->rlower - s->rupper + 1; if (region_height == 0) region_height = 1; @@ -1706,23 +1681,10 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg, placement = image_placement_create(gd, im, input, app_image_id, app_placement_id, z); /* - * The rows above origin_y were only ever needed to make the image - * fit on screen at all - screen_write_scrollup() already pushed - * blank rows into history to make room, so those absolute grid rows - * exist and are otherwise unused. Give them spans too, instead of - * silently discarding that part of the image: unlike width, which - * has no "scroll right" to recover a permanent clip, height already - * has ordinary scrollback - it would be wasted if these rows were - * left with nothing to show when scrolled back to. - * - * screen_write_scrollup() clamps its own lines argument to at most - * one screen height per call, however many were requested, so it - * may not have created a history row for every one of the origin_y - * rows this is trying to place - cap to however many actually - * exist (gd->hsize) to avoid reading before the start of the grid, - * and place the rows closest to the visible area (the highest - * source rows below origin_y) in whatever history space there is, - * since those are the ones the scrolled-off area would show first. + * Give the origin_y rows scrolled into history spans too, so they + * show when scrolled back to instead of appearing blank - cap to + * gd->hsize since screen_write_scrollup() may not have created a + * history row for every one of them. */ hist_origin_y = origin_y; if (hist_origin_y > gd->hsize) diff --git a/screen-redraw.c b/screen-redraw.c index 93e3a60b0..6f5e5830d 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1633,6 +1633,7 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp, struct redraw_line *line; struct redraw_spans *spans; struct redraw_span *span; + enum redraw_image_phase phase; u_int cy; int y, top, bottom; @@ -1670,8 +1671,8 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp, } #endif - for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE; - phase <= REDRAW_IMAGES_AFTER; phase++) { + for (phase = REDRAW_IMAGES_BEFORE; phase <= REDRAW_IMAGES_AFTER; + phase++) { for (y = top; y < bottom; y++) { line = &scene->lines[y]; if (dctx->flags & REDRAW_STATUS_TOP) @@ -1714,10 +1715,11 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags) struct redraw_line *line; struct redraw_spans *spans; struct redraw_span *span; + enum redraw_image_phase phase; u_int y, cy, type; - for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE; - phase <= REDRAW_IMAGES_AFTER; phase++) { + for (phase = REDRAW_IMAGES_BEFORE; phase <= REDRAW_IMAGES_AFTER; + phase++) { for (y = 0; y < scene->sy; y++) { line = &scene->lines[y]; if (dctx->flags & REDRAW_STATUS_TOP) @@ -1725,45 +1727,46 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags) else cy = y; for (type = 0; type < REDRAW_SPAN_TYPES; type++) { - if (phase != REDRAW_TEXT && type != REDRAW_SPAN_PANE) + if (phase != REDRAW_TEXT && + type != REDRAW_SPAN_PANE) continue; - if (!REDRAW_IS_ALL(flags)) { - switch (type) { - case REDRAW_SPAN_PANE: - if (~flags & REDRAW_PANE) + if (!REDRAW_IS_ALL(flags)) { + switch (type) { + case REDRAW_SPAN_PANE: + if (~flags & REDRAW_PANE) + continue; + break; + case REDRAW_SPAN_OUTSIDE: + if (~flags & REDRAW_OUTSIDE) + continue; + break; + case REDRAW_SPAN_EMPTY: + if (~flags & REDRAW_EMPTY) + continue; + break; + case REDRAW_SPAN_BORDER: + if (~flags & REDRAW_PANE_BORDER) + continue; + break; + case REDRAW_SPAN_STATUS: + if (~flags & REDRAW_PANE_STATUS) + continue; + break; + case REDRAW_SPAN_SCROLLBAR: + if (~flags & REDRAW_PANE_SCROLLBAR) + continue; + break; + case REDRAW_SPAN_MENU: + if (~flags & REDRAW_MENU) + continue; + break; + default: continue; - break; - case REDRAW_SPAN_OUTSIDE: - if (~flags & REDRAW_OUTSIDE) - continue; - break; - case REDRAW_SPAN_EMPTY: - if (~flags & REDRAW_EMPTY) - continue; - break; - case REDRAW_SPAN_BORDER: - if (~flags & REDRAW_PANE_BORDER) - continue; - break; - case REDRAW_SPAN_STATUS: - if (~flags & REDRAW_PANE_STATUS) - continue; - break; - case REDRAW_SPAN_SCROLLBAR: - if (~flags & REDRAW_PANE_SCROLLBAR) - continue; - break; - case REDRAW_SPAN_MENU: - if (~flags & REDRAW_MENU) - continue; - break; - default: - continue; + } } - } - spans = &line->spans[type]; - TAILQ_FOREACH(span, spans, entry) - redraw_draw_span(dctx, span, cy, phase); + spans = &line->spans[type]; + TAILQ_FOREACH(span, spans, entry) + redraw_draw_span(dctx, span, cy, phase); } } #ifdef ENABLE_IMAGES @@ -2210,6 +2213,7 @@ redraw_draw_damage_rectangle(struct redraw_draw_ctx *dctx, u_int x, u_int y, struct redraw_line *line; struct redraw_spans *spans; struct redraw_span *span; + enum redraw_image_phase phase; u_int cy, yy, type; if (x >= scene->sx || y >= scene->sy) @@ -2223,18 +2227,12 @@ redraw_draw_damage_rectangle(struct redraw_draw_ctx *dctx, u_int x, u_int y, #ifdef ENABLE_IMAGES /* - * Remove any stale Kitty placements this redraw is about to replace, - * the same as redraw_draw_pane_lines() does for a full pane redraw - - * unlike a plain overwrite of SIXEL pixels, a Kitty placement is a - * discrete object that persists until explicitly deleted, so without - * this a scroll-triggered redraw (this function, not the full-pane - * path) leaves every previous placement behind, all still visible - * and now overlapping the newly placed ones. - * - * Every span type is included, not just panes: when a floating pane - * moves, cells where its image was placed can now belong to a border - * (or anything else), and a Kitty placement left there would stay - * drawn over it. + * Remove stale Kitty placements this redraw is about to replace, the + * same as redraw_draw_pane_lines() does for a full pane redraw - a + * placement persists until explicitly deleted, unlike a plain SIXEL + * overwrite. Every span type is included, not just panes, since a + * moved floating pane can leave a placement over cells that now + * belong to something else. */ for (yy = y; yy < y + sy; yy++) { line = &scene->lines[yy]; @@ -2258,8 +2256,8 @@ redraw_draw_damage_rectangle(struct redraw_draw_ctx *dctx, u_int x, u_int y, } #endif - for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE; - phase <= REDRAW_IMAGES_AFTER; phase++) { + for (phase = REDRAW_IMAGES_BEFORE; phase <= REDRAW_IMAGES_AFTER; + phase++) { for (yy = y; yy < y + sy; yy++) { line = &scene->lines[yy]; if (dctx->flags & REDRAW_STATUS_TOP) @@ -2267,28 +2265,26 @@ redraw_draw_damage_rectangle(struct redraw_draw_ctx *dctx, u_int x, u_int y, else cy = yy; for (type = 0; type < REDRAW_SPAN_TYPES; type++) { - if (phase != REDRAW_TEXT && type != REDRAW_SPAN_PANE) + if (phase != REDRAW_TEXT && + type != REDRAW_SPAN_PANE) continue; if (type == REDRAW_SPAN_STATUS) continue; - spans = &line->spans[type]; - TAILQ_FOREACH(span, spans, entry) { - if (span->x >= x + sx) - continue; - if (span->x + span->width <= x) - continue; - if (type == REDRAW_SPAN_STATUS) { - redraw_damage_refresh_status(dctx, - span->data.st.wp); - } - redraw_draw_span(dctx, span, cy, phase); - if (phase == REDRAW_TEXT && type == REDRAW_SPAN_PANE) { - redraw_damage_draw_pane_prompt(dctx, - span, cy); + spans = &line->spans[type]; + TAILQ_FOREACH(span, spans, entry) { + if (span->x >= x + sx) + continue; + if (span->x + span->width <= x) + continue; + redraw_draw_span(dctx, span, cy, phase); + if (phase == REDRAW_TEXT && + type == REDRAW_SPAN_PANE) { + redraw_damage_draw_pane_prompt( + dctx, span, cy); + } } } } - } #ifdef ENABLE_IMAGES image_draw_flush(&scene->c->tty); #endif diff --git a/tmux.h b/tmux.h index 00a6eef5c..58b96bf32 100644 --- a/tmux.h +++ b/tmux.h @@ -1805,12 +1805,12 @@ struct tty_term { #define TERM_SIXEL 0x40 #define TERM_INVALIDMS 0x80 #define TERM_KITTY 0x100 +#define TERM_NOREPLACE 0x200 #ifdef ENABLE_IMAGES -#define TERM_IMAGE_QUADRANTS 0x200 -#define TERM_IMAGE_SEXTANTS 0x400 +#define TERM_IMAGE_QUADRANTS 0x400 +#define TERM_IMAGE_SEXTANTS 0x800 #define TERM_IMAGESCROLL 0x1000 #endif -#define TERM_NOREPLACE 0x800 int flags; LIST_ENTRY(tty_term) entry; diff --git a/tty.c b/tty.c index 77c9fa52d..cbb620c7a 100644 --- a/tty.c +++ b/tty.c @@ -585,20 +585,12 @@ tty_update_features(struct tty *tty) tty_puts(tty, tty_term_string(tty->term, TTYC_ENESC)); /* - * 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. + * Features might have changed since the first draw during attach, for + * example on a DA response - but only redraw if something actually + * did change, since this is called for every DA answer (which are + * usually confirming what is already known) and a redraw repaints + * from tmux's own grid, discarding anything the pane put on the + * terminal that tmux does not model (e.g. a DCS-passthrough image). */ if (!changed) return; diff --git a/window-copy.c b/window-copy.c index bc59520b2..6bef8c679 100644 --- a/window-copy.c +++ b/window-copy.c @@ -5241,21 +5241,10 @@ window_copy_write_one(struct window_mode_entry *wme, #ifdef ENABLE_IMAGES /* * Write image-covered cells directly into the grid, - * skipping both window_copy_update_style() (so a - * selection, current-line or search-mark highlight - * never sweeps visibly over the image before it is - * recomposited separately - see the - * image_redraw_area() call below) and - * screen_write_cell(), whose built-in - * screen_write_image_damage() call (screen-write.c) - * fires on every write regardless of whether - * anything actually changed, which would needlessly - * re-damage - and so retransmit - the image on every - * redraw. The cell must still land in the grid as - * normal: a non-graphical client's ASCII fallback for - * the image is an ordinary character here, not - * something recomposited separately, and depends on - * this write the same as any other cell. + * skipping window_copy_update_style() (a highlight + * must not sweep over the image) and + * screen_write_cell() (its image-damage call would + * re-damage the image on every redraw for nothing). */ if (image_grid_check_area(gd, fx, fy, gc.data.width, 1)) { @@ -5516,18 +5505,11 @@ window_copy_write_line(struct window_mode_entry *wme, #ifdef ENABLE_IMAGES /* - * Copy the backing line's image layers separately from its text - * cells. This is not implied by the text cells just written above: - * those go via the pane's normal (frequently fast, direct-write) - * path, which knows nothing about image content. - * - * Only report it as needing a redraw when the view has actually - * moved (data->image_refresh, set once per window_copy_redraw_lines - * call) - text writes never touch image-covered cells (see - * window_copy_write_one), so if the view is unmoved this row's - * images are already exactly as they should be and redrawing them - * anyway just flashes the image on every unrelated redraw (e.g. - * every step of a selection drag) for no visible benefit. + * Copy the backing line's image layers separately: the text write + * above knows nothing about image content. Only redraw them when the + * view has actually moved (data->image_refresh) - otherwise they are + * already correct and redrawing would just flash them on every + * unrelated redraw. */ image_grid_free_line(s->grid, &s->grid->linedata[s->grid->hsize + py]); @@ -5600,17 +5582,10 @@ window_copy_redraw_selection(struct window_mode_entry *wme, u_int old_y) #ifdef ENABLE_IMAGES /* * Only rows whose underlying history position has moved since the last - * call need their images recomposited - most redraws are just a selection - * or cursor-line style change with the view otherwise unmoved, and that - * never touches image-covered cells (see window_copy_write_one()), so - * redrawing images for it is needless: on a fast drag it is visible as the - * image briefly flashing on every step even though nothing about it - * actually changed. - * - * Every place that calls window_copy_write_line()/window_copy_write_lines() - * must call this first - it is not implied by them, since some (the - * initial full-screen draw on entering copy mode, window_copy_scroll_up(), - * window_copy_scroll_down()) write directly rather than going through + * call need their images recomposited, to avoid flashing them on every + * unrelated redraw. Every caller of window_copy_write_line()/ + * window_copy_write_lines() must call this first - it is not implied by + * them, since some write directly rather than via * window_copy_redraw_lines(). */ static void @@ -5626,14 +5601,9 @@ window_copy_update_image_refresh(struct window_copy_mode_data *data) /* * Whether any part of the currently visible backing range carries image - * data. A terminal's line insert/delete only shifts character cells - the - * pixels of a sixel or Kitty image already on screen stay exactly where - * they were sent, so a scroll that uses that fast path (see - * window_copy_scroll_up()/window_copy_scroll_down()) leaves stale or - * missing image content for every row except the single new one it - * explicitly rewrites. When this returns true, callers should fall back to - * a full window_copy_redraw_screen() instead, so every visible row's image - * is recomposited at its correct new position. + * data. A scrolled insert/delete-line fast path only shifts character + * cells, leaving image content stale, so callers should fall back to a + * full window_copy_redraw_screen() when this returns true. */ static int window_copy_visible_has_images(struct window_copy_mode_data *data)