diff --git a/include/notcurses.h b/include/notcurses.h index 1856a1be3..01d29a4a1 100644 --- a/include/notcurses.h +++ b/include/notcurses.h @@ -1491,13 +1491,6 @@ cell_noforeground_p(const cell* c){ return cell_simple_p(c) && isspace(c->gcluster); } -// True if the cell does not generate background pixels. Only the FULL BLOCK -// glyph has this property, AFAIK. -static inline bool -cell_nobackground_p(const struct ncplane* n, const cell* c){ - return !cell_simple_p(c) && !strcmp(cell_extended_gcluster(n, c), "\xe2\x96\x88"); -} - static inline int cell_load_simple(struct ncplane* n, cell* c, char ch){ cell_release(n, c); diff --git a/src/lib/internal.h b/src/lib/internal.h index 471c5e61c..8bbfc8c61 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -320,6 +320,14 @@ cell_debug(const ncplane* p, const cell* c){ } } +// True if the cell does not generate background pixels. Only the FULL BLOCK +// glyph has this property, AFAIK. +// FIXME set a bit, doing this at load time +static inline bool +cell_nobackground_p(const egcpool* e, const cell* c){ + return !cell_simple_p(c) && !strcmp(egcpool_extended_gcluster(e, c), "\xe2\x96\x88"); +} + // no CLOCK_MONOTONIC_RAW on FreeBSD as of 12.0 :/ #ifndef CLOCK_MONOTONIC_RAW #define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC diff --git a/src/lib/render.c b/src/lib/render.c index 71fb29c88..7d7e16608 100644 --- a/src/lib/render.c +++ b/src/lib/render.c @@ -11,6 +11,7 @@ mutex_unlock(void* vlock){ static int blocking_write(int fd, const char* buf, size_t buflen){ +//fprintf(stderr, "writing %zu to %d...\n", buflen, fd); size_t written = 0; do{ ssize_t w = write(fd, buf + written, buflen - written); @@ -82,7 +83,7 @@ update_render_stats(const struct timespec* time1, const struct timespec* time0, // 256 colors, this is the 16 normal ones, 6x6x6 color cubes, and 32 greys. // it's probably better to sample the darker regions rather than cover so much // chroma, but whatever....FIXME -static inline int +/*static inline int prep_optimized_palette(notcurses* nc, FILE* out __attribute__ ((unused))){ if(nc->RGBflag){ return 0; // DirectColor, no need to write palette @@ -92,7 +93,7 @@ prep_optimized_palette(notcurses* nc, FILE* out __attribute__ ((unused))){ } // FIXME return 0; -} +}*/ // reshape the shadow framebuffer to match the stdplane's dimensions, throwing // away the old one. @@ -244,7 +245,7 @@ visible_cell(cell* c, int y, int x, ncplane* n, int* previousz){ // number of columns occupied by this EGC (only an approximation; it's actually // a property of the font being used). static int -term_putc(FILE* out, const ncplane* n, const cell* c){ +term_putc(FILE* out, const egcpool* e, const cell* c){ if(cell_simple_p(c)){ if(c->gcluster == 0 || iscntrl(c->gcluster)){ // fprintf(stderr, "[ ]\n"); @@ -266,7 +267,7 @@ term_putc(FILE* out, const ncplane* n, const cell* c){ } } }else{ - const char* ext = extended_gcluster(n, c); + const char* ext = egcpool_extended_gcluster(e, c); // fprintf(stderr, "[%s]\n", ext); #ifdef __USE_GNU if(fputs_unlocked(ext, out) < 0){ // FIXME check for short write? @@ -483,23 +484,161 @@ cellcmp_and_dupfar(egcpool* dampool, cell* damcell, const ncplane* srcplane, return 1; } +// Producing the frame requires three steps: +// * render -- build up a flat framebuffer from a set of ncplanes +// * rasterize -- build up a UTF-8 stream of escapes and EGCs +// * refresh -- write the stream to the emulator static inline int -notcurses_render_internal(notcurses* nc){ +notcurses_rasterize(notcurses* nc, unsigned char* damagemap){ + FILE* out = nc->rstate.mstreamfp; int ret = 0; int y, x; - FILE* out = nc->rstate.mstreamfp; fseeko(out, 0, SEEK_SET); + // we only need to emit a coordinate if it was damaged. the damagemap is a + // bit per coordinate, rows by rows, column by column within a row, with the + // MSB being the first coordinate. + size_t damageidx = 0; + unsigned char damagemask = 0x80; // 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. - prep_optimized_palette(nc, out); // FIXME do what on failure? - // if this fails, struggle bravely on. we can live without a lastframe. - reshape_shadow_fb(nc); for(y = 0 ; y < nc->stdscr->leny ; ++y){ // how many characters have we elided? it's not worthwhile to invoke a // cursor movement with cup if we only elided one or two. set to INT_MAX // whenever we're on a new line. int needmove = INT_MAX; + for(x = 0 ; x < nc->stdscr->lenx ; ++x){ + unsigned r, g, b, br, bg, bb; + const cell* srccell = &nc->lastframe[y * nc->lfdimx + x]; +// cell c; +// memcpy(c, srccell, sizeof(*c)); // unsafe copy of gcluster +//fprintf(stderr, "COPYING: %d from %p\n", c->gcluster, &nc->pool); +// const char* egc = pool_egc_copy(&nc->pool, srccell); +// c->gcluster = 0; // otherwise cell_release() will blow up +//fprintf(stderr, "idx: %zu word: 0x%02x\n", damageidx, damagemap[damageidx]); + if((damagemap[damageidx] & damagemask) == 0){ + // no need to emit a cell; what we rendered appears to already be + // here. no updates are performed to elision state nor lastframe. + ++nc->stats.cellelisions; + if(needmove < INT_MAX){ + ++needmove; + } + if(cell_double_wide_p(srccell)){ + if(needmove < INT_MAX){ + ++needmove; + } + ++nc->stats.cellelisions; + } + }else{ + ++nc->stats.cellemissions; + if(needmove == 1 && nc->cuf1){ + ret |= term_emit("cuf1", tiparm(nc->cuf1), out, false); + }else if(needmove){ + ret |= term_emit("cup", tiparm(nc->cup, y, x), out, false); + } + needmove = 0; + // set the style. this can change the color back to the default; if it + // does, we need update our elision possibilities. + bool normalized; + ret |= term_setstyles(nc, out, &nc->rstate.curattr, srccell, &normalized); + if(normalized){ + nc->rstate.defaultelidable = true; + nc->rstate.bgelidable = false; + nc->rstate.fgelidable = false; + } + // we allow these to be set distinctly, but terminfo only supports using + // them both via the 'op' capability. unless we want to generate the 'op' + // escapes ourselves, if either is set to default, we first send op, and + // then a turnon for whichever aren't default. + + // if our cell has a default foreground *or* background, we can elide the + // default set iff one of: + // * we are a partial glyph, and the previous was default on both, or + // * we are a no-foreground glyph, and the previous was default background, or + // * we are a no-background glyph, and the previous was default foreground + bool noforeground = cell_noforeground_p(srccell); + bool nobackground = cell_nobackground_p(&nc->pool, srccell); + if((!noforeground && cell_fg_default_p(srccell)) || (!nobackground && cell_bg_default_p(srccell))){ + if(!nc->rstate.defaultelidable){ + ++nc->stats.defaultemissions; + ret |= term_emit("op", nc->op, out, false); + }else{ + ++nc->stats.defaultelisions; + } + // if either is not default, this will get turned off + nc->rstate.defaultelidable = true; + nc->rstate.fgelidable = false; + nc->rstate.bgelidable = false; + } + + // if our cell has a non-default foreground, we can elide the non-default + // foreground set iff either: + // * the previous was non-default, and matches what we have now, or + // * we are a no-foreground glyph (iswspace() is true) + if(!cell_fg_default_p(srccell)){ + if(!noforeground){ + cell_fg_rgb(srccell, &r, &g, &b); + if(nc->rstate.fgelidable && nc->rstate.lastr == r && nc->rstate.lastg == g && nc->rstate.lastb == b){ + ++nc->stats.fgelisions; + }else{ + ret |= term_fg_rgb8(nc, out, r, g, b); + ++nc->stats.fgemissions; + nc->rstate.fgelidable = true; + } + nc->rstate.lastr = r; nc->rstate.lastg = g; nc->rstate.lastb = b; + nc->rstate.defaultelidable = false; + }else{ + ++nc->stats.fgelisions; + } + } + if(!cell_bg_default_p(srccell)){ + if(!nobackground){ + cell_bg_rgb(srccell, &br, &bg, &bb); + if(nc->rstate.bgelidable && nc->rstate.lastbr == br && nc->rstate.lastbg == bg && nc->rstate.lastbb == bb){ + ++nc->stats.bgelisions; + }else{ + ret |= term_bg_rgb8(nc, out, br, bg, bb); + ++nc->stats.bgemissions; + nc->rstate.bgelidable = true; + } + nc->rstate.lastbr = br; nc->rstate.lastbg = bg; nc->rstate.lastbb = bb; + nc->rstate.defaultelidable = false; + }else{ + ++nc->stats.bgelisions; + } + } +//fprintf(stderr, "[%02d/%02d] 0x%02x 0x%02x 0x%02x\n", y, x, r, g, b); + ret |= term_putc(out, &nc->pool, srccell); + } + if((damagemask >>= 1u) == 0){ + damagemask = 0x80; + ++damageidx; + } + } + } + ret |= fflush(out); + //fflush(nc->ttyfp); + if(blocking_write(nc->ttyfd, nc->rstate.mstream, nc->rstate.mstrsize)){ + ret = -1; + } +/*fprintf(stderr, "%lu/%lu %lu/%lu %lu/%lu\n", defaultelisions, defaultemissions, + fgelisions, fgemissions, bgelisions, bgemissions);*/ + if(nc->renderfp){ + fprintf(nc->renderfp, "%s\n", nc->rstate.mstream); + } + if(ret < 0){ + return ret; + } + return nc->rstate.mstrsize; +} + +static inline int +notcurses_render_internal(notcurses* nc, unsigned char* damagevec){ + int ret = 0; + int y, x; + // if this fails, struggle bravely on. we can live without a lastframe. + reshape_shadow_fb(nc); + for(y = 0 ; y < nc->stdscr->leny ; ++y){ // track the depth of our glyph, to see if we need need to stomp a wide // glyph we're following. int depth = 0; @@ -507,7 +646,6 @@ notcurses_render_internal(notcurses* nc){ // anything, *BUT* we must handle higher planes bisecting our wide glyph. bool inright = false; for(x = 0 ; x < nc->stdscr->lenx ; ++x){ - unsigned r, g, b, br, bg, bb; ncplane* p; cell c; // no need to initialize p = visible_cell(&c, y, x, nc->top, &depth); @@ -517,7 +655,6 @@ notcurses_render_internal(notcurses* nc){ if((x + 1 >= nc->stdscr->lenx && cell_double_wide_p(&c))){ continue; // needmove will be reset as we restart the line } - bool damaged = false; if(depth > 0){ // we are above the previous source plane if(inright){ // wipe out the character to the left // FIXME do this by keeping an offset for the memstream, and @@ -527,16 +664,12 @@ notcurses_render_internal(notcurses* nc){ cell_init(prev); // FIXME technically we need rerun the visible cell search...? gross cell_load_simple(NULL, prev, ' '); - // FIXME this space will be the wrong color, methinks? - term_emit("cup", tiparm(nc->cup, y, x - 1), out, false); - fputc(' ', out); inright = false; //if(cell_simple_p(&c)){ //fprintf(stderr, "WENT BACK NOW FOR %c\n", c.gcluster); //}else{ //fprintf(stderr, "WENT BACK NOW FOR %s\n", extended_gcluster(p, &c)); //} - damaged = true; } } // lastframe has already been sized to match the current size, so no need @@ -548,120 +681,16 @@ notcurses_render_internal(notcurses* nc){ inright = false; continue; } - // check the damage map, unless we must repair damage done - if(cellcmp_and_dupfar(&nc->pool, oldcell, p, &c) == 0){ - if(!damaged){ - // no need to emit a cell; what we rendered appears to already be - // here. no updates are performed to elision state nor lastframe. - ++nc->stats.cellelisions; - if(needmove < INT_MAX){ - ++needmove; - } - if(cell_double_wide_p(&c)){ - if(needmove < INT_MAX){ - ++needmove; - } - ++nc->stats.cellelisions; - inright = !inright; - }else{ - inright = false; - } - continue; - } - } - } - ++nc->stats.cellemissions; - if(needmove == 1 && nc->cuf1){ - ret |= term_emit("cuf1", tiparm(nc->cuf1), out, false); - }else if(needmove){ - ret |= term_emit("cup", tiparm(nc->cup, y, x), out, false); - } - needmove = 0; - // set the style. this can change the color back to the default; if it - // does, we need update our elision possibilities. - bool normalized; - term_setstyles(nc, out, &nc->rstate.curattr, &c, &normalized); - if(normalized){ - nc->rstate.defaultelidable = true; - nc->rstate.bgelidable = false; - nc->rstate.fgelidable = false; - } - // we allow these to be set distinctly, but terminfo only supports using - // them both via the 'op' capability. unless we want to generate the 'op' - // escapes ourselves, if either is set to default, we first send op, and - // then a turnon for whichever aren't default. - - // if our cell has a default foreground *or* background, we can elide the - // default set iff one of: - // * we are a partial glyph, and the previous was default on both, or - // * we are a no-foreground glyph, and the previous was default background, or - // * we are a no-background glyph, and the previous was default foreground - bool noforeground = cell_noforeground_p(&c); - bool nobackground = cell_nobackground_p(p, &c); - if((!noforeground && cell_fg_default_p(&c)) || (!nobackground && cell_bg_default_p(&c))){ - if(!nc->rstate.defaultelidable){ - ++nc->stats.defaultemissions; - term_emit("op", nc->op, out, false); - }else{ - ++nc->stats.defaultelisions; - } - // if either is not default, this will get turned off - nc->rstate.defaultelidable = true; - nc->rstate.fgelidable = false; - nc->rstate.bgelidable = false; - } - - // if our cell has a non-default foreground, we can elide the non-default - // foreground set iff either: - // * the previous was non-default, and matches what we have now, or - // * we are a no-foreground glyph (iswspace() is true) - if(!cell_fg_default_p(&c)){ - if(!noforeground){ - cell_fg_rgb(&c, &r, &g, &b); - if(nc->rstate.fgelidable && nc->rstate.lastr == r && nc->rstate.lastg == g && nc->rstate.lastb == b){ - ++nc->stats.fgelisions; - }else{ - term_fg_rgb8(nc, out, r, g, b); - ++nc->stats.fgemissions; - nc->rstate.fgelidable = true; - } - nc->rstate.lastr = r; nc->rstate.lastg = g; nc->rstate.lastb = b; - nc->rstate.defaultelidable = false; - }else{ - ++nc->stats.fgelisions; - } - } - if(!cell_bg_default_p(&c)){ - if(!nobackground){ - cell_bg_rgb(&c, &br, &bg, &bb); - if(nc->rstate.bgelidable && nc->rstate.lastbr == br && nc->rstate.lastbg == bg && nc->rstate.lastbb == bb){ - ++nc->stats.bgelisions; - }else{ - term_bg_rgb8(nc, out, br, bg, bb); - ++nc->stats.bgemissions; - nc->rstate.bgelidable = true; - } - nc->rstate.lastbr = br; nc->rstate.lastbg = bg; nc->rstate.lastbb = bb; - nc->rstate.defaultelidable = false; - }else{ - ++nc->stats.bgelisions; + // check the damage map + if(cellcmp_and_dupfar(&nc->pool, oldcell, p, &c)){ +//fprintf(stderr, "setting damagevec idx %d mask %u\n", (y * nc->stdscr->lenx + x) / CHAR_BIT, (0x80 >> ((y * nc->stdscr->lenx + x) % CHAR_BIT))); + damagevec[(y * nc->stdscr->lenx + x) / CHAR_BIT] |= + (0x80 >> ((y * nc->stdscr->lenx + x) % CHAR_BIT)); } } -//fprintf(stderr, "[%02d/%02d] 0x%02x 0x%02x 0x%02x %p\n", y, x, r, g, b, p); - term_putc(out, p, &c); inright = cell_double_wide_p(&c); } } - ret |= fflush(out); - //fflush(nc->ttyfp); - if(blocking_write(nc->ttyfd, nc->rstate.mstream, nc->rstate.mstrsize)){ - ret = -1; - } -/*fprintf(stderr, "%lu/%lu %lu/%lu %lu/%lu\n", defaultelisions, defaultemissions, - fgelisions, fgemissions, bgelisions, bgemissions);*/ - if(nc->renderfp){ - fprintf(nc->renderfp, "%s\n", nc->rstate.mstream); - } if(ret){ return ret; } @@ -674,7 +703,15 @@ int notcurses_render(notcurses* nc){ clock_gettime(CLOCK_MONOTONIC_RAW, &start); pthread_mutex_lock(&nc->lock); pthread_cleanup_push(mutex_unlock, &nc->lock); - int bytes = notcurses_render_internal(nc); + int bytes = -1; + size_t damagevecsize = nc->stdscr->leny * nc->stdscr->lenx / CHAR_BIT + + !!(nc->stdscr->leny * nc->stdscr->lenx % CHAR_BIT); + unsigned char* damagevec = malloc(damagevecsize); + memset(damagevec, 0, damagevecsize); + if(notcurses_render_internal(nc, damagevec) >= 0){ + bytes = notcurses_rasterize(nc, damagevec); + } + free(damagevec); int dimy, dimx; notcurses_resize(nc, &dimy, &dimx); clock_gettime(CLOCK_MONOTONIC_RAW, &done);