diff --git a/src/lib/internal.h b/src/lib/internal.h index a45155427..1c584bc85 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -49,10 +49,11 @@ struct ncvisual_details; // and we can't go throwing C++ syntax into this header. so it goes. typedef enum { - SPRIXEL_QUIESCENT, // sprixel has been drawn - SPRIXEL_INVALIDATED, // sprixel needs to be redrawn - SPRIXEL_HIDE, // sprixel queued for destruction - SPRIXEL_MOVED, // sprixel needs be moved + SPRIXEL_QUIESCENT, // up-to-date and visible at the proper place + SPRIXEL_LOADED, // loaded, but not yet made visible (kitty-only) + SPRIXEL_INVALIDATED, // not up-to-date, need reload, trumps MOVED + SPRIXEL_HIDE, // queued for destruction + SPRIXEL_MOVED, // visible, up-to-date, but in the wrong place } sprixel_e; // elements of the T-A matrix describe transparency and annihilation at a @@ -796,6 +797,7 @@ void sprixel_invalidate(sprixel* s, int y, int x); void sprixel_movefrom(sprixel* s, int y, int x); void sprixel_debug(const sprixel* s, FILE* out); void sixelmap_free(struct sixelmap *s); +int kitty_commit(FILE* fp, sprixel* s); // make loaded image visible // create an auxiliary vector suitable for a sprixcell, and zero it out. there // are two bytes per pixel in the cell. kitty uses only one (for an alpha diff --git a/src/lib/kitty.c b/src/lib/kitty.c index 12328746b..5addeb245 100644 --- a/src/lib/kitty.c +++ b/src/lib/kitty.c @@ -417,6 +417,13 @@ int kitty_wipe(sprixel* s, int ycell, int xcell){ return -1; } +int kitty_commit(FILE* fp, sprixel* s){ + loginfo("Committing Kitty graphic id %u\n", s->id); + fprintf(fp, "\e_Ga=p,i=%u,p=1\e\\", s->id); + s->invalidated = SPRIXEL_QUIESCENT; + return 0; +} + // we can only write 4KiB at a time. we're writing base64-encoded RGBA. each // pixel is 4B raw (32 bits). each chunk of three pixels is then 12 bytes, or // 16 base64-encoded bytes. 4096 / 16 == 256 3-pixel groups, or 768 pixels. @@ -446,7 +453,7 @@ write_kitty_data(FILE* fp, int linesize, int leny, int lenx, int cols, //fprintf(stderr, "total: %d chunks = %d, s=%d,v=%d\n", total, chunks, lenx, leny); while(chunks--){ if(totalout == 0){ - *parse_start = fprintf(fp, "\e_Gf=32,s=%d,v=%d,i=%d,p=1,a=T,%c=1%s;", + *parse_start = fprintf(fp, "\e_Gf=32,s=%d,v=%d,i=%d,p=1,a=t,%c=1%s;", lenx, leny, sprixelid, chunks ? 'm' : 'q', scroll ? "" : ",C=1"); }else{ @@ -615,7 +622,7 @@ int kitty_draw(const ncpile* p, sprixel* s, FILE* out){ if(fwrite(s->glyph, s->glyphlen, 1, out) != 1){ ret = -1; } - s->invalidated = SPRIXEL_QUIESCENT; + s->invalidated = SPRIXEL_LOADED; return ret; } diff --git a/src/lib/render.c b/src/lib/render.c index 39001f14f..f62a517d5 100644 --- a/src/lib/render.c +++ b/src/lib/render.c @@ -833,16 +833,29 @@ emit_bg_palindex(notcurses* nc, FILE* out, const nccell* srccell){ return 0; } -// remove any sprixels which are no longer desired. for kitty, this will be -// a pure erase; for sixel, we must overwrite. returns -1 on failure, and -// otherwise the number of bytes emitted redrawing sprixels. +// this first phase of sprixel rasterization is responsible for: +// 1) invalidating all QUIESCENT sprixels if the pile has changed (because +// it would have been destroyed when switching away from our pile). +// for the same reason, invalidated all MOVE sprixels in this case. +// 2) damaging all cells under a HIDE sixel, so text phase 1 consumes it +// (not necessary for kitty graphics) +// 3) damaging uncovered cells under a MOVE (not necessary for kitty) +// 4) drawing invalidated sixels and loading invalidated kitty graphics +// (new kitty graphics are *not* yet made visible) +// by the end of this pass, all sixels are *complete*. all kitty graphics +// are loaded, but old kitty graphics remain visible, and new/updated kitty +// graphics are not yet visible, and they have not moved. static int64_t clean_sprixels(notcurses* nc, ncpile* p, FILE* out){ sprixel* s; sprixel** parent = &p->sprixelcache; int64_t bytesemitted = 0; while( (s = *parent) ){ - if(s->invalidated == SPRIXEL_HIDE){ + if(s->invalidated == SPRIXEL_QUIESCENT){ + if(p != nc->last_pile){ + s->invalidated = SPRIXEL_INVALIDATED; + } + }else if(s->invalidated == SPRIXEL_HIDE){ //fprintf(stderr, "OUGHT HIDE %d [%dx%d] %p\n", s->id, s->dimy, s->dimx, s); if(sprite_scrub(nc, p, s)){ return -1; @@ -851,13 +864,18 @@ clean_sprixels(notcurses* nc, ncpile* p, FILE* out){ s->next->prev = s->prev; } sprixel_free(s); - }else if(s->invalidated == SPRIXEL_MOVED || s->invalidated == SPRIXEL_INVALIDATED){ + continue; // don't account as an elision + } + if(s->invalidated == SPRIXEL_MOVED || s->invalidated == SPRIXEL_INVALIDATED){ int y, x; ncplane_yx(s->n, &y, &x); //fprintf(stderr, "1 MOVING BITMAP %d STATE %d AT %d/%d for %p\n", s->id, s->invalidated, y + nc->margin_t, x + nc->margin_l, s->n); - // without this, kitty flickers if(s->invalidated == SPRIXEL_MOVED){ - sprite_scrub(nc, p, s); + if(p != nc->last_pile){ + s->invalidated = SPRIXEL_INVALIDATED; + }else{ // this is a new pile, so we couldn't have been on-screen + //sprite_scrub(nc, p, s); + } } if(goto_location(nc, out, y + nc->margin_t, x + nc->margin_l) == 0){ int r = sprite_redraw(nc, p, s, out); @@ -873,6 +891,7 @@ clean_sprixels(notcurses* nc, ncpile* p, FILE* out){ ++nc->stats.sprixelelisions; parent = &s->next; } +//fprintf(stderr, "SPRIXEL STATE: %d\n", s->invalidated); } return bytesemitted; } @@ -889,8 +908,13 @@ rasterize_scrolls(ncpile* p, FILE* out){ return 0; } -// draw any invalidated sprixels. returns -1 on error, number of bytes written -// on success. any material underneath them has already been updated. +// second sprixel pass in rasterization. by this time, all sixels are handled +// (and in the QUIESCENT state); only persistent kitty graphics still require +// operation. responsibilities of this second pass include: +// +// 1) if we're a different pile, issue the kitty universal clear +// 2) first, hide all sprixels in the HIDE state +// 3) then, make allo LOADED sprixels visible static int64_t rasterize_sprixels(notcurses* nc, ncpile* p, FILE* out){ int64_t bytesemitted = 0; @@ -898,7 +922,7 @@ rasterize_sprixels(notcurses* nc, ncpile* p, FILE* out){ if(s->invalidated == SPRIXEL_INVALIDATED){ int y, x; ncplane_yx(s->n, &y, &x); -//fprintf(stderr, "3 DRAWING BITMAP %d STATE %d AT %d/%d for %p\n", s->id, s->invalidated, y + nc->margin_t, x + nc->margin_l, s->n); +fprintf(stderr, "3 DRAWING BITMAP %d STATE %d AT %d/%d for %p\n", s->id, s->invalidated, y + nc->margin_t, x + nc->margin_l, s->n); if(goto_location(nc, out, y + nc->margin_t, x + nc->margin_l)){ return -1; } @@ -909,6 +933,18 @@ rasterize_sprixels(notcurses* nc, ncpile* p, FILE* out){ bytesemitted += r; nc->rstate.hardcursorpos = true; ++nc->stats.sprixelemissions; + }else if(s->invalidated == SPRIXEL_LOADED){ + if(nc->tcache.pixel_commit){ + int y,x; + ncplane_yx(s->n, &y, &x); +//fprintf(stderr, "3 DRAWING BITMAP %d STATE %d AT %d/%d for %p\n", s->id, s->invalidated, y + nc->margin_t, x + nc->margin_l, s->n); + if(goto_location(nc, out, y + nc->margin_t, x + nc->margin_l)){ + return -1; + } + if(nc->tcache.pixel_commit(out, s) < 0){ + return -1; + } + } } } return bytesemitted; @@ -1050,25 +1086,26 @@ rasterize_core(notcurses* nc, const ncpile* p, FILE* out, unsigned phase){ // desired; in this case, an ASU footer is present at the end of the buffer. static int notcurses_rasterize_inner(notcurses* nc, ncpile* p, FILE* out, unsigned* asu){ +//fprintf(stderr, "pile %p ymax: %d xmax: %d\n", p, p->dimy + nc->margin_t, p->dimx + nc->margin_l); // we only need to emit a coordinate if it was damaged. the damagemap is a // bit per coordinate, one per struct crender. // don't write a clearscreen. we only update things that have been changed. // we explicitly move the cursor at the beginning of each output line, so no // need to home it expliticly. -//fprintf(stderr, "pile %p ymax: %d xmax: %d\n", p, p->dimy + nc->margin_t, p->dimx + nc->margin_l); +//fprintf(stderr, "RASTERIZE SPRIXELS\n"); int64_t sprixelbytes = clean_sprixels(nc, p, out); if(sprixelbytes < 0){ return -1; } update_palette(nc, out); -//fprintf(stderr, "RASTERIZE CORE\n"); if(rasterize_scrolls(p, out)){ return -1; } +//fprintf(stderr, "RASTERIZE CORE PASS 1\n"); if(rasterize_core(nc, p, out, 0)){ return -1; } -//fprintf(stderr, "RASTERIZE SPRIXELS\n"); +//fprintf(stderr, "FINALIZE SPRIXELS\n"); int64_t rasprixelbytes = rasterize_sprixels(nc, p, out); if(rasprixelbytes < 0){ return -1; @@ -1077,7 +1114,7 @@ notcurses_rasterize_inner(notcurses* nc, ncpile* p, FILE* out, unsigned* asu){ pthread_mutex_lock(&nc->statlock); nc->stats.sprixelbytes += sprixelbytes; pthread_mutex_unlock(&nc->statlock); -//fprintf(stderr, "RASTERIZE CORE\n"); +//fprintf(stderr, "RASTERIZE CORE PASS 2\n"); if(rasterize_core(nc, p, out, 1)){ return -1; } @@ -1119,13 +1156,6 @@ raster_and_write(notcurses* nc, ncpile* p, FILE* out){ return -1; } } - // if the last pile was different from this one, we need clear all old - // sprixels (and invalidate all those of the current pile -- FIXME). - if(nc->last_pile != p && nc->last_pile){ - if(sprite_clear_all(&nc->tcache, out)){ - return -1; - } - } if(notcurses_rasterize_inner(nc, p, out, &useasu) < 0){ return -1; } diff --git a/src/lib/sixel.c b/src/lib/sixel.c index f5ddd8eef..3981730c2 100644 --- a/src/lib/sixel.c +++ b/src/lib/sixel.c @@ -845,13 +845,12 @@ int sixel_draw(const ncpile* p, sprixel* s, FILE* out){ } } } - s->invalidated = SPRIXEL_INVALIDATED; - }else{ - if(fwrite(s->glyph, s->glyphlen, 1, out) != 1){ - return -1; - } - s->invalidated = SPRIXEL_QUIESCENT; } + if(fwrite(s->glyph, s->glyphlen, 1, out) != 1){ + return -1; + } + s->invalidated = SPRIXEL_QUIESCENT; +fprintf(stderr, "POSTDRAW: %d %d/%d\n", s->invalidated, s->movedfromy, s->movedfromx); return s->glyphlen; } diff --git a/src/lib/termdesc.c b/src/lib/termdesc.c index 169effc2b..f8278725d 100644 --- a/src/lib/termdesc.c +++ b/src/lib/termdesc.c @@ -70,6 +70,7 @@ setup_kitty_bitmaps(tinfo* ti, int fd, int sixel_maxy_pristine){ ti->pixel_scrub = kitty_scrub; ti->pixel_remove = kitty_remove; ti->pixel_draw = kitty_draw; + ti->pixel_commit = kitty_commit; ti->pixel_move = kitty_move; ti->pixel_shutdown = kitty_shutdown; ti->sprixel_scale_height = 1; @@ -256,7 +257,9 @@ grow_esc_table(tinfo* ti, const char* tstr, escape_e esc, // these three queries (terminated with a Primary Device Attributes, to which // all known terminals reply) hopefully can uniquely and unquestionably // identify the terminal to which we are talking. -#define IDQUERIES "\x1b[=0c" /* Tertiary Device Attributes */ \ +// FIXME Konsole doesn't currently handle TDA, so we have to consume the 0c +// in our state machine =[ +#define IDQUERIES "\x1b[=c" /* Tertiary Device Attributes */ \ "\x1b[>0q" /* XTVERSION */ \ "\x1bP+q544e\x1b\\" /* XTGETTCAP['TN'] */ @@ -267,19 +270,29 @@ grow_esc_table(tinfo* ti, const char* tstr, escape_e esc, // thing is, if we get a response to this, we know we can use it for u7! #define DSRCPR "\e[6n" +// check for Synchronized Update Mode support. the p is necessary, but Konsole +// doesn't consume it, so we have to handle that in our state machine =[. +#define SUMQUERY "\x1b[?2026$p" + +// XTSMGRAPHICS query for the number of color registers. +#define CREGSXTSM "\x1b[?2;1;0S" + +// XTSMGRAPHICS query for the maximum supported geometry. +#define GEOMXTSM "\x1b[?1;1;0S" + #define DIRECTIVES CSI_BGQ \ DSRCPR \ - "\x1b[?2026$p" /* query for Synchronized Updates */ \ + SUMQUERY \ "\x1b[?1;3;256S" /* try to set 256 cregs */ \ - "\x1b[?2;1;0S" /* XTSMGRAPHICS (cregs) */ \ - "\x1b[?1;1;0S" /* XTSMGRAPHICS (geometry) */ \ + CREGSXTSM \ + GEOMXTSM \ "\x1b[c" /* Device Attributes */ // we send an XTSMGRAPHICS to set up 256 color registers (the most we can // currently take advantage of; we need at least 64 to use sixel at all. // maybe that works, maybe it doesn't. then query both color registers // and geometry. send XTGETTCAP for terminal name. if 'minimal' is set, don't -// send any identification queries. +// send any identification queries (we've already identified the terminal). static int send_initial_queries(int fd, bool minimal){ const char *queries; diff --git a/src/lib/termdesc.h b/src/lib/termdesc.h index 49b084b3b..c3e70e511 100644 --- a/src/lib/termdesc.h +++ b/src/lib/termdesc.h @@ -136,6 +136,8 @@ typedef struct tinfo { int (*pixel_scrub)(const struct ncpile* p, struct sprixel* s); int (*pixel_shutdown)(FILE* fp); // called during context shutdown int (*pixel_clear_all)(FILE* fp); // called during context startup + // make a loaded graphic visible. only used with kitty. + int (*pixel_commit)(FILE* fp, struct sprixel* s); uint8_t* (*pixel_trans_auxvec)(const struct tinfo* ti); // create tranparent auxvec // sprixel parameters. there are several different sprixel protocols, of // which we support sixel and kitty. the kitty protocol is used based