Skip to content

Commit

Permalink
[kitty] multiframe annihilation transitivity
Browse files Browse the repository at this point in the history
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!
  • Loading branch information
dankamongmen committed Sep 11, 2021
1 parent 86ea9d0 commit fb8b6bf
Showing 1 changed file with 68 additions and 11 deletions.
79 changes: 68 additions & 11 deletions src/lib/kitty.c
Original file line number Diff line number Diff line change
Expand Up @@ -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){
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)){
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit fb8b6bf

Please sign in to comment.