[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!
pull/2162/head
nick black 3 years ago committed by nick black
parent 86ea9d00d0
commit fb8b6bfcae

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

Loading…
Cancel
Save