kitty: separate out commit from draw #1865

pull/1895/head
nick black 3 years ago committed by Nick Black
parent 3608d6a2d6
commit 3f8dcfa357

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

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

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

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

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

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

Loading…
Cancel
Save