From fb8b6bfcaecaa4eb5285f69b0fcc0068ac02540e Mon Sep 17 00:00:00 2001 From: nick black Date: Sat, 11 Sep 2021 00:13:20 -0400 Subject: [PATCH] [kitty] multiframe annihilation transitivity When using the reflexive composition extension to the Kitty graphics protocol, we refer to the previously- blitted image to rebuild cells, and thus don't have to keep a copy of the image ourselves. We just blit null- alpha cells to wipe, and then execute a reflective composition to rebuild. The auxvector is a single word, holding the state we were in before the wipe, since we don't have a copy of the image to look at to determine the state afresh (and caching it is more efficient anyway). Good, good. Except. When we reload the plane, we want to carry over those wipe cells, yes? I.e. if there was a plane above the graphic before, and we replace the graphic, that plane still better be above it. And that means editing the cell out of the new graphic that we blit up, on the fly. Which we were doing. Problem is, if we later need to rebuild that cell, and we reflexively compose using this new image, *we reflexively compose the edited-out cell*, and thus remain wiped (our RGB values get set properly, but we have all 0 alphas). No good! So instead, in KITTY_SELFREF (the most advanced kitty implementation, corresponding to reflexive composition), do a pass at the end and send AAAAA wipes for any ANNIHILATED{_TRANS} sprixcells. We don't present until both have hit (since we're using a new image ID), so there's no flicker, though there is some bandwidth cost. That handles rebuilding. Problem is, if we then need to wipe these same sprixcells *again*, our auxvecs were set to ANNIHILATED{_TRANS}, and thus no wipe takes place. Rather than blindly propagating the auxvec across the frames, properly reset the auxvec according to the new blit. This handles wiping post rebuild, closing the cycle. Kitty 0.22.0+ now runs the bitmapstates PoC perfectly. Closes #2143. woo-hah! --- src/lib/kitty.c | 79 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 11 deletions(-) diff --git a/src/lib/kitty.c b/src/lib/kitty.c index bb213d92e..6b7847e75 100644 --- a/src/lib/kitty.c +++ b/src/lib/kitty.c @@ -413,16 +413,10 @@ int kitty_wipe_animation(sprixel* s, int ycell, int xcell){ return 1; } -// FIXME merge back with kitty_wipe_animation -int kitty_wipe_selfref(sprixel* s, int ycell, int xcell){ - if(init_sprixel_animation(s)){ - return -1; - } - const int tyx = xcell + ycell * s->dimx; - int state = s->n->tam[tyx].state; - void* auxvec = s->n->tam[tyx].auxvector; - logdebug("Wiping sprixel %u at %d/%d auxvec: %p state: %d\n", s->id, ycell, xcell, auxvec, state); - fbuf* f = &s->glyph; +// just dump the wipe into the fbuf -- don't manipulate any state. used both +// by the wipe proper, and when blitting a new frame with annihilations. +static int +kitty_blit_wipe_selfref(sprixel* s, fbuf* f, int ycell, int xcell){ if(fbuf_printf(f, "\e_Ga=f,x=%d,y=%d,s=%d,v=%d,i=%d,X=1,r=2,c=1,q=2;", xcell * s->cellpxx, ycell * s->cellpxy, s->cellpxx, s->cellpxy, s->id) < 0){ @@ -455,6 +449,22 @@ int kitty_wipe_selfref(sprixel* s, int ycell, int xcell){ if(fbuf_printf(f, "\e\\\e_Ga=a,i=%d,c=2,q=2;\e\\", s->id) < 0){ return -1; } + return 0; +} + +// FIXME merge back with kitty_wipe_animation +int kitty_wipe_selfref(sprixel* s, int ycell, int xcell){ + if(init_sprixel_animation(s)){ + return -1; + } + const int tyx = xcell + ycell * s->dimx; + int state = s->n->tam[tyx].state; + void* auxvec = s->n->tam[tyx].auxvector; + logdebug("Wiping sprixel %u at %d/%d auxvec: %p state: %d\n", s->id, ycell, xcell, auxvec, state); + fbuf* f = &s->glyph; + if(kitty_blit_wipe_selfref(s, f, ycell, xcell)){ + return -1; + } s->invalidated = SPRIXEL_INVALIDATED; memcpy(auxvec, &state, sizeof(state)); return 1; @@ -721,6 +731,31 @@ destroy_deflator(unsigned animated, z_stream* zctx, int pixy, int pixx){ } } +// if we're KITTY_SELFREF, and we're blitting a secondary frame, we need +// carry through the TAM's annihilation entires...but we also need load the +// frame *without* annihilations, lest we be unable to build it. we thus go +// back through the TAM following a selfref blit, and any sprixcells which +// are annihilated will have their annhilation appended to the main blit. +// ought only be called for KITTY_SELFREF. +static int +finalize_multiframe_selfref(sprixel* s, fbuf* f){ + int prewiped = 0; + for(int y = 0 ; y < s->dimy ; ++y){ + for(int x = 0 ; x < s->dimx ; ++x){ + int tyxidx = y * s->dimx + x; + int state = s->n->tam[tyxidx].state; + if(state >= SPRIXCELL_ANNIHILATED){ + if(kitty_blit_wipe_selfref(s, f, y, x)){ + return -1; + } + ++prewiped; + } + } + } + loginfo("transitively wiped %d/%d\n", prewiped, s->dimy * s->dimx); + 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. @@ -756,6 +791,9 @@ write_kitty_data(fbuf* f, int linesize, int leny, int lenx, int cols, int targetout = 0; // number of pixels expected out after this chunk //fprintf(stderr, "total: %d chunks = %d, s=%d,v=%d\n", total, chunks, lenx, leny); char out[17]; // three pixels base64 to no more than 17 bytes + // set high if we are (1) reloading a frame with (2) annihilated cells copied over + // from the TAM and (3) we are KITTY_SELFREF. calls finalize_multiframe_selfref(). + bool selfref_annihilated = false; while(chunks--){ // q=2 has been able to go on chunks other than the last chunk since // 2021-03, but there's no harm in this small bit of backwards compat. @@ -836,16 +874,30 @@ write_kitty_data(fbuf* f, int linesize, int leny, int lenx, int cols, // transparent, but we need to update the auxiliary vector. const int vyx = (y % cdimy) * cdimx + (x % cdimx); tam[tyx].auxvector[vyx] = ncpixel_a(source[e]); + wipe[e] = 1; + }else if(level == KITTY_SELFREF){ + selfref_annihilated = true; + }else{ + wipe[e] = 1; } if(rgba_trans_p(source[e], transcolor)){ ncpixel_set_a(&source[e], 0); // in case it was transcolor if(x % cdimx == 0 && y % cdimy == 0){ tam[tyx].state = SPRIXCELL_ANNIHILATED_TRANS; + if(level == KITTY_SELFREF){ + *tam[tyx].auxvector = SPRIXCELL_TRANSPARENT; + } + }else if(level == KITTY_SELFREF && tam[tyx].state == SPRIXCELL_ANNIHILATED_TRANS){ + *tam[tyx].auxvector = SPRIXCELL_MIXED_KITTY; } }else{ + if(x % cdimx == 0 && y % cdimy == 0 && level == KITTY_SELFREF){ + *tam[tyx].auxvector = SPRIXCELL_OPAQUE_KITTY; + }else if(level == KITTY_SELFREF && *tam[tyx].auxvector == SPRIXCELL_TRANSPARENT){ + *tam[tyx].auxvector = SPRIXCELL_MIXED_KITTY; + } tam[tyx].state = SPRIXCELL_ANNIHILATED; } - wipe[e] = 1; }else{ wipe[e] = 0; if(rgba_trans_p(source[e], transcolor)){ @@ -889,6 +941,11 @@ write_kitty_data(fbuf* f, int linesize, int leny, int lenx, int cols, if(finalize_deflator(&zctx, f, leny, lenx)){ goto err; } + if(selfref_annihilated){ + if(finalize_multiframe_selfref(s, f)){ + goto err; + } + } } scrub_tam_boundaries(tam, leny, lenx, cdimy, cdimx); destroy_deflator(animated, &zctx, leny, lenx);