From 7ec022d067a72d203f34b1421d78151a532bd2f1 Mon Sep 17 00:00:00 2001 From: Nick Black Date: Tue, 14 Jan 2020 03:51:11 -0500 Subject: [PATCH] Painter's algorithm #255 (#274) * notcursesI.avi 30 -> 60fps * painter's algorithm works #255 --- src/lib/fade.c | 2 +- src/lib/internal.h | 9 +- src/lib/notcurses.c | 22 +- src/lib/render.c | 513 ++++++++++++++++++++------------------------ tests/libav.cpp | 2 + tests/ncplane.cpp | 26 +++ 6 files changed, 285 insertions(+), 289 deletions(-) diff --git a/src/lib/fade.c b/src/lib/fade.c index d9491f6a9..dd00e0305 100644 --- a/src/lib/fade.c +++ b/src/lib/fade.c @@ -28,7 +28,7 @@ alloc_ncplane_palette(ncplane* n, planepalette* pp){ int y, x; for(y = 0 ; y < pp->rows ; ++y){ for(x = 0 ; x < pp->cols ; ++x){ - channels = n->fb[fbcellidx(n, y, x)].channels; + channels = n->fb[nfbcellidx(n, y, x)].channels; pp->channels[y * pp->cols + x] = channels; channels_fg_rgb(channels, &r, &g, &b); if(r > pp->maxr){ diff --git a/src/lib/internal.h b/src/lib/internal.h index 8bbfc8c61..231e93502 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -206,8 +206,13 @@ ncplane_unlock(const ncplane* n){ } static inline int -fbcellidx(const ncplane* n, int row, int col){ - return row * n->lenx + col; +fbcellidx(int row, int rowlen, int col){ + return row * rowlen + col; +} + +static inline int +nfbcellidx(const ncplane* n, int row, int col){ + return fbcellidx(row, n->lenx, col); } // copy the UTF8-encoded EGC out of the cell, whether simple or complex. the diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index c6806848f..ae94a3d3f 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -162,7 +162,7 @@ int ncplane_at_cursor(ncplane* n, cell* c){ if(cursor_invalid_p(n)){ return -1; } - return cell_duplicate(n, c, &n->fb[fbcellidx(n, n->y, n->x)]); + return cell_duplicate(n, c, &n->fb[nfbcellidx(n, n->y, n->x)]); } int ncplane_at_yx(ncplane* n, int y, int x, cell* c){ @@ -170,7 +170,7 @@ int ncplane_at_yx(ncplane* n, int y, int x, cell* c){ pthread_mutex_lock(&n->nc->lock); if(y < n->leny && x < n->lenx){ if(y >= 0 && x >= 0){ - ret = cell_duplicate(n, c, &n->fb[fbcellidx(n, y, x)]); + ret = cell_duplicate(n, c, &n->fb[nfbcellidx(n, y, x)]); } } pthread_mutex_unlock(&n->nc->lock); @@ -178,7 +178,7 @@ int ncplane_at_yx(ncplane* n, int y, int x, cell* c){ } cell* ncplane_cell_ref_yx(ncplane* n, int y, int x){ - return &n->fb[fbcellidx(n, y, x)]; + return &n->fb[nfbcellidx(n, y, x)]; } void ncplane_dim_yx(ncplane* n, int* rows, int* cols){ @@ -384,7 +384,7 @@ ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny, copyoff += -xoff; copied += -xoff; } - const int sourceidx = fbcellidx(n, sourceline, keepx); + const int sourceidx = nfbcellidx(n, sourceline, keepx); memcpy(fb + copyoff, preserved + sourceidx, sizeof(*fb) * keeplenx); copyoff += keeplenx; copied += keeplenx; @@ -1098,13 +1098,13 @@ int ncplane_putc_yx(ncplane* n, int y, int x, const cell* c){ // that cell as wide). Any character placed atop one half of a wide character // obliterates the other half. Note that a wide char can thus obliterate two // wide chars, totalling four columns. - cell* targ = &n->fb[fbcellidx(n, n->y, n->x)]; + cell* targ = &n->fb[nfbcellidx(n, n->y, n->x)]; if(n->x > 0){ if(cell_double_wide_p(targ)){ // replaced cell is half of a wide char if(targ->gcluster == 0){ // we're the right half - cell_obliterate(n, &n->fb[fbcellidx(n, n->y, n->x - 1)]); + cell_obliterate(n, &n->fb[nfbcellidx(n, n->y, n->x - 1)]); }else{ - cell_obliterate(n, &n->fb[fbcellidx(n, n->y, n->x + 1)]); + cell_obliterate(n, &n->fb[nfbcellidx(n, n->y, n->x + 1)]); } } } @@ -1115,17 +1115,17 @@ int ncplane_putc_yx(ncplane* n, int y, int x, const cell* c){ int cols = 1; if(wide){ ++cols; - cell* rtarg = &n->fb[fbcellidx(n, n->y, n->x + 1)]; + cell* rtarg = &n->fb[nfbcellidx(n, n->y, n->x + 1)]; cell_release(n, rtarg); cell_init(rtarg); cell_set_wide(rtarg); } if(wide){ // must set our right wide, and check for further damage if(n->x < n->lenx - 1){ // check to our right - cell* candidate = &n->fb[fbcellidx(n, n->y, n->x + 1)]; + cell* candidate = &n->fb[nfbcellidx(n, n->y, n->x + 1)]; if(n->x < n->lenx - 2){ if(cell_wide_left_p(candidate)){ - cell_obliterate(n, &n->fb[fbcellidx(n, n->y, n->x + 2)]); + cell_obliterate(n, &n->fb[nfbcellidx(n, n->y, n->x + 2)]); } } cell_set_wide(candidate); @@ -1163,7 +1163,7 @@ int ncplane_cursor_at(const ncplane* n, cell* c, char** gclust){ if(n->y == n->leny && n->x == n->lenx){ return -1; } - const cell* src = &n->fb[fbcellidx(n, n->y, n->x)]; + const cell* src = &n->fb[nfbcellidx(n, n->y, n->x)]; memcpy(c, src, sizeof(*src)); *gclust = NULL; if(!cell_simple_p(src)){ diff --git a/src/lib/render.c b/src/lib/render.c index 7d7e16608..cbcbff7d2 100644 --- a/src/lib/render.c +++ b/src/lib/render.c @@ -78,23 +78,6 @@ update_render_stats(const struct timespec* time1, const struct timespec* time0, } } -// determine the best palette for the current frame, and write the necessary -// escape sequences to 'out'. for now, we just assume the ANSI palettes. at -// 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 -prep_optimized_palette(notcurses* nc, FILE* out __attribute__ ((unused))){ - if(nc->RGBflag){ - return 0; // DirectColor, no need to write palette - } - if(!nc->CCCflag){ - return 0; // can't change palette - } - // FIXME - return 0; -}*/ - // reshape the shadow framebuffer to match the stdplane's dimensions, throwing // away the old one. static int @@ -123,122 +106,234 @@ reshape_shadow_fb(notcurses* nc){ return 0; } -// Find the topmost cell for this coordinate by walking down the z-buffer, -// looking for an intersecting ncplane. Once we've found one, check it for -// transparency in either the back- or foreground. If the alpha channel is -// active, keep descending and blending until we hit opacity, or bedrock. We -// recurse to find opacity, and blend the result into what we have. The -// 'findfore' and 'findback' bools control our recursion--there's no point in -// going further down when a color is locked in, so don't (for instance) recurse -// further when we have a transparent foreground and opaque background atop an -// opaque foreground and transparent background. The cell we ultimately return -// (a const ref to 'c') is backed by '*retp' via rawdog copy; the caller must -// not call cell_release() upon it, nor use it beyond the scope of the render. -// -// So, as we go down, we find planes which can have impact on the result. Once -// we've locked the result in (base case), write the deep values we have to 'c'. -// Then, as we come back up, blend them as appropriate. The actual glyph is -// whichever one occurs at the top with a non-transparent α (α < 2). To effect -// tail recursion, though, we instead write first, and then recurse, blending -// as we descend. α == 0 is opaque. α == 2 is fully transparent. -// -// It is useful to know how deep our glyph came from (the depth of the return -// value), so it will be recorded in 'previousz'. It is useful to know this -// value's relation to the previous cell, so the previous value is provided as -// input to 'previousz', and when we set 'previousz' in this function, we use -// the positive depth to indicate that the return value was above the previous -// plane, and a negative depth to indicate that the return value was equal to or -// below the previous plane. Relative depths are valid only within the context -// of a single render. 'previousz' must be non-negative on input. -static inline ncplane* -dig_visible_cell(cell* c, int y, int x, ncplane* p, int* previousz){ - int depth = 1; - unsigned fgblends = 0; - unsigned bgblends = 0; - // once we decide on our glyph, it cannot be changed by anything below, so - // lock in this plane for the actual cell return. - ncplane* glyphplane = NULL; - while(p){ - // where in the plane this coordinate would be, based off absy/absx. the - // true origin is 0,0, so abs=2,2 means coordinate 3,3 would be 1,1, while - // abs=-2,-2 would make coordinate 3,3 relative 5,5. - int poffx, poffy; - poffy = y - p->absy; - poffx = x - p->absx; - if(poffy < p->leny && poffy >= 0){ - if(poffx < p->lenx && poffx >= 0){ // p is valid for this y, x - const cell* vis = &p->fb[fbcellidx(p, poffy, poffx)]; - // if we never loaded any content into the cell (or obliterated it by - // writing in a zero), use the plane's default cell. - if(vis->gcluster == 0){ - vis = &p->basecell; - } - // if we have no character in this cell, we continue to look for a - // character, but our foreground color will still be used unless it's - // been set to transparent. if that foreground color is transparent, we - // still use a character we find here, but its color will come entirely - // from cells underneath us. - if(!glyphplane){ - if( (c->gcluster = vis->gcluster) ){ // index copy only - glyphplane = p; - } - if(cell_double_wide_p(vis)){ - cell_set_wide(c); - glyphplane = p; - } - if(glyphplane){ - c->attrword = vis->attrword; +static inline void +pool_release(egcpool* pool, cell* c){ + if(!cell_simple_p(c)){ + egcpool_release(pool, cell_egc_idx(c)); + c->gcluster = 0; // don't subject ourselves to double-release problems + } +} + +void cell_release(ncplane* n, cell* c){ + pool_release(&n->pool, c); +} + +// Duplicate one cell onto another, possibly crossing ncplanes. +static inline int +cell_duplicate_far(egcpool* tpool, cell* targ, const ncplane* splane, const cell* c){ + pool_release(tpool, targ); + targ->attrword = c->attrword; + targ->channels = c->channels; + if(cell_simple_p(c)){ + targ->gcluster = c->gcluster; + return !!c->gcluster; + } + size_t ulen = strlen(extended_gcluster(splane, c)); +//fprintf(stderr, "[%s] (%zu)\n", egcpool_extended_gcluster(&splane->pool, c), strlen(egcpool_extended_gcluster(&splane->pool, c))); + int eoffset = egcpool_stash(tpool, extended_gcluster(splane, c), ulen); + if(eoffset < 0){ + return -1; + } + targ->gcluster = eoffset + 0x80; + return ulen; +} + +// Duplicate one cell onto another when they share a plane. Convenience wrapper. +int cell_duplicate(ncplane* n, cell* targ, const cell* c){ + return cell_duplicate_far(&n->pool, targ, n, c); +} + +// the heart of damage detection. compare two cells (from two different planes) +// for equality. if they are equal, return 0. otherwise, dup the second onto +// the first and return non-zero. +static int +cellcmp_and_dupfar(egcpool* dampool, cell* damcell, const ncplane* srcplane, + const cell* srccell){ + if(damcell->attrword == srccell->attrword){ + if(damcell->channels == srccell->channels){ + bool damsimple = cell_simple_p(damcell); + bool srcsimple = cell_simple_p(srccell); + if(damsimple == srcsimple){ + if(damsimple){ + if(damcell->gcluster == srccell->gcluster){ + return 0; // simple match } - } - if(cell_fg_alpha(c) > CELL_ALPHA_OPAQUE && cell_fg_alpha(vis) < CELL_ALPHA_TRANSPARENT){ - cell_blend_fchannel(c, cell_fchannel(vis), fgblends); - ++fgblends; - } - // Background color takes effect independently of whether we have a - // glyph. If we've already locked in the background, it has no effect. - // If it's transparent, it has no effect. Otherwise, update the - // background channel and balpha. - if(cell_bg_alpha(c) > CELL_ALPHA_OPAQUE && cell_bg_alpha(vis) < CELL_ALPHA_TRANSPARENT){ - cell_blend_bchannel(c, cell_bchannel(vis), bgblends); - ++bgblends; - } - // if everything's locked in, we're done - if((glyphplane && cell_fg_alpha(c) == CELL_ALPHA_OPAQUE && - cell_bg_alpha(c) == CELL_ALPHA_OPAQUE)){ - if(depth < *previousz){ - *previousz = depth; - }else{ - *previousz = -depth; + }else{ + const char* damegc = egcpool_extended_gcluster(dampool, damcell); + const char* srcegc = extended_gcluster(srcplane, srccell); + if(strcmp(damegc, srcegc) == 0){ + return 0; // EGC match } - return glyphplane; } } } - p = p->z; - ++depth; } - // if we have a background set, but no glyph selected, load a space so that - // the background will be printed - if(!glyphplane){ - cell_load_simple(NULL, c, ' '); + cell_duplicate_far(dampool, damcell, srcplane, srccell); + return 1; +} + +// Is this cell locked in? I.e. does it have all three of: +// * a selected EGC +// * CELL_ALPHA_OPAQUE foreground channel +// * CELL_ALPHA_OPAQUE background channel +static inline bool +cell_locked_p(const cell* p){ + if(p->gcluster || cell_double_wide_p(p)){ + if(cell_fg_alpha(p) == CELL_ALPHA_OPAQUE){ + if(cell_bg_alpha(p) == CELL_ALPHA_OPAQUE){ + return 1; + } + } } - if(depth < *previousz){ - *previousz = depth; - }else{ - *previousz = -depth; + return 0; +} + +// Extracellular state for a cell during the render process +struct crender { + int fgblends; + int bgblends; + ncplane *p; + bool damaged; +}; + +// Paints a single ncplane into the provided framebuffer 'fb'. Whenever a cell +// is locked in, it is compared against the last frame. If it is different, the +// 'damagevec' bitmap is updated with a 1. +static int +paint(notcurses* nc, ncplane* p, struct crender* rvec, cell* fb){ + int y, x, dimy, dimx, offy, offx; + // don't use ncplane_dim_yx()/ncplane_yx() here, lest we deadlock + dimy = p->leny; + dimx = p->lenx; + offy = p->absy; + offx = p->absx; +//fprintf(stderr, "PLANE %p %d %d %d %d %d %d\n", p, dimy, dimx, offy, offx, nc->stdscr->leny, nc->stdscr->lenx); + for(y = 0 ; y < dimy ; ++y){ + for(x = 0 ; x < dimx ; ++x){ + int absy = y + offy; + int absx = x + offx; + if(absy < 0 || absy >= nc->stdscr->leny){ + continue; + } + if(absx < 0 || absx >= nc->stdscr->lenx){ + continue; + } + cell* targc = &fb[fbcellidx(absy, nc->stdscr->lenx, absx)]; + if(cell_locked_p(targc)){ + continue; + } + struct crender* crender = &rvec[fbcellidx(absy, nc->stdscr->lenx, absx)]; + const cell* vis = &p->fb[nfbcellidx(p, y, x)]; + // if we never loaded any content into the cell (or obliterated it by + // writing in a zero), use the plane's default cell. + if(vis->gcluster == 0){ + vis = &p->basecell; + } + // if we have no character in this cell, we continue to look for a + // character, but our foreground color will still be used unless it's + // been set to transparent. if that foreground color is transparent, we + // still use a character we find here, but its color will come entirely + // from cells underneath us. + if(!crender->p){ + // if the following is true, we're a real glyph, and not the right-h + // hand side of a wide glyph (or the null codepoint). + if( (targc->gcluster = vis->gcluster) ){ // index copy only + // we can't plop down a wide glyph if the next cell is beyond the + // screen, nor if we're bisected by a higher plane. + if(cell_double_wide_p(vis)){ + // are we on the last column of the real screen? if so, 0x20 us + if(absx >= nc->stdscr->lenx - 1){ + targc->gcluster = ' '; + // is the next cell occupied? if so, 0x20 us + }else if(targc[1].gcluster){ +//fprintf(stderr, "NULLING out %d/%d (%d/%d) due to %u\n", y, x, absy, absx, targc[1].gcluster); + targc->gcluster = ' '; + }else{ + cell_set_wide(targc); + } + } + crender->p = p; + targc->attrword = vis->attrword; + }else if(cell_double_wide_p(vis)){ + cell_set_wide(targc); + } + } + if(cell_fg_alpha(targc) > CELL_ALPHA_OPAQUE && cell_fg_alpha(vis) < CELL_ALPHA_TRANSPARENT){ + cell_blend_fchannel(targc, cell_fchannel(vis), crender->fgblends); + ++crender->fgblends; + } + // Background color takes effect independently of whether we have a + // glyph. If we've already locked in the background, it has no effect. + // If it's transparent, it has no effect. Otherwise, update the + // background channel and balpha. + if(cell_bg_alpha(targc) > CELL_ALPHA_OPAQUE && cell_bg_alpha(vis) < CELL_ALPHA_TRANSPARENT){ + cell_blend_bchannel(targc, cell_bchannel(vis), crender->bgblends); + ++crender->bgblends; + } + + if(cell_locked_p(targc)){ + cell* prevcell = &nc->lastframe[fbcellidx(absy, nc->lfdimx, absx)]; + if(cellcmp_and_dupfar(&nc->pool, prevcell, crender->p, targc)){ +/*if(cell_simple_p(prevcell)){ +fprintf(stderr, "WROTE %u [%c] to %d/%d (%d/%d)\n", prevcell->gcluster, prevcell->gcluster, y, x, absy, absx); +}else{ +fprintf(stderr, "WROTE %u [%s] to %d/%d (%d/%d)\n", prevcell->gcluster, egcpool_extended_gcluster(&nc->pool, prevcell), y, x, absy, absx); +}*/ + crender->damaged = true; + } + } + } } - return glyphplane; + return 0; } -static inline ncplane* -visible_cell(cell* c, int y, int x, ncplane* n, int* previousz){ - cell_init(c); - cell_set_fg_alpha(c, CELL_ALPHA_TRANSPARENT); - cell_set_bg_alpha(c, CELL_ALPHA_TRANSPARENT); - if(*previousz < 0){ - *previousz = -*previousz; +// We execute the painter's algorithm, starting from our topmost plane. The +// damagevector should be all zeros on input. On success, it will reflect +// which cells were changed. We solve for each coordinate's cell by walking +// down the z-buffer, looking at intersections with ncplanes. This implies +// locking down the EGC, the attributes, and the channels for each cell. +static inline int +notcurses_render_internal(notcurses* nc, struct crender* rvec){ + if(reshape_shadow_fb(nc)){ + return -1; } - return dig_visible_cell(c, y, x, n, previousz); + int dimy, dimx; + notcurses_term_dim_yx(nc, &dimy, &dimx); + cell* fb = malloc(sizeof(*fb) * dimy * dimx); + for(int y = 0 ; y < dimy ; ++y){ + for(int x = 0 ; x < dimx ; ++x){ + cell* c = &fb[fbcellidx(y, dimx, x)]; + c->gcluster = 0; + c->channels = 0; + c->attrword = 0; + cell_set_fg_alpha(c, CELL_ALPHA_TRANSPARENT); + cell_set_bg_alpha(c, CELL_ALPHA_TRANSPARENT); + } + } + ncplane* p = nc->top; + while(p){ + if(paint(nc, p, rvec, fb)){ + return -1; + } + p = p->z; + } + for(int y = 0 ; y < dimy ; ++y){ + for(int x = 0 ; x < dimx ; ++x){ + cell* targc = &fb[fbcellidx(y, dimx, x)]; + if(!cell_locked_p(targc)){ + cell* prevcell = &nc->lastframe[fbcellidx(y, dimx, x)]; + if(targc->gcluster == 0){ + targc->gcluster = ' '; + } + if(cellcmp_and_dupfar(&nc->pool, prevcell, rvec->p, targc)){ + struct crender* crender = &rvec[fbcellidx(y, dimx, x)]; + crender->damaged = true; + } + } + } + } + free(fb); + return 0; } // write the cell's UTF-8 grapheme cluster to the provided FILE*. returns the @@ -418,78 +513,12 @@ term_fg_rgb8(notcurses* nc, FILE* out, unsigned r, unsigned g, unsigned b){ return 0; } -static inline void -pool_release(egcpool* pool, cell* c){ - if(!cell_simple_p(c)){ - egcpool_release(pool, cell_egc_idx(c)); - c->gcluster = 0; // don't subject ourselves to double-release problems - } -} - -void cell_release(ncplane* n, cell* c){ - pool_release(&n->pool, c); -} - -// Duplicate one cell onto another, possibly crossing ncplanes. -static inline int -cell_duplicate_far(egcpool* tpool, cell* targ, const ncplane* splane, const cell* c){ - pool_release(tpool, targ); - targ->attrword = c->attrword; - targ->channels = c->channels; - if(cell_simple_p(c)){ - targ->gcluster = c->gcluster; - return !!c->gcluster; - } - size_t ulen = strlen(extended_gcluster(splane, c)); -// fprintf(stderr, "[%s] (%zu)\n", extended_gcluster(n, c), strlen(extended_gcluster(n, c))); - int eoffset = egcpool_stash(tpool, extended_gcluster(splane, c), ulen); - if(eoffset < 0){ - return -1; - } - targ->gcluster = eoffset + 0x80; - return ulen; -} - -// Duplicate one cell onto another when they share a plane. Convenience wrapper. -int cell_duplicate(ncplane* n, cell* targ, const cell* c){ - return cell_duplicate_far(&n->pool, targ, n, c); -} - -// the heart of damage detection. compare two cells (from two different planes) -// for equality. if they are equal, return 0. otherwise, dup the second onto -// the first and return non-zero. -static int -cellcmp_and_dupfar(egcpool* dampool, cell* damcell, const ncplane* srcplane, - const cell* srccell){ - if(damcell->attrword == srccell->attrword){ - if(damcell->channels == srccell->channels){ - bool damsimple = cell_simple_p(damcell); - bool srcsimple = cell_simple_p(srccell); - if(damsimple == srcsimple){ - if(damsimple){ - if(damcell->gcluster == srccell->gcluster){ - return 0; // simple match - } - }else{ - const char* damegc = egcpool_extended_gcluster(dampool, damcell); - const char* srcegc = extended_gcluster(srcplane, srccell); - if(strcmp(damegc, srcegc) == 0){ - return 0; // EGC match - } - } - } - } - } - cell_duplicate_far(dampool, damcell, srcplane, srccell); - 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_rasterize(notcurses* nc, unsigned char* damagemap){ +notcurses_rasterize(notcurses* nc, const struct crender* rvec){ FILE* out = nc->rstate.mstreamfp; int ret = 0; int y, x; @@ -498,15 +527,14 @@ notcurses_rasterize(notcurses* nc, unsigned char* damagemap){ // 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. 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; + // whenever we're on a new line. leave room to avoid overflow. + int needmove = INT_MAX - nc->stdscr->lenx; for(x = 0 ; x < nc->stdscr->lenx ; ++x){ unsigned r, g, b, br, bg, bb; const cell* srccell = &nc->lastframe[y * nc->lfdimx + x]; @@ -515,18 +543,13 @@ notcurses_rasterize(notcurses* nc, unsigned char* damagemap){ //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){ + if(!rvec[damageidx].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; - } + ++needmove; if(cell_double_wide_p(srccell)){ - if(needmove < INT_MAX){ - ++needmove; - } + ++needmove; ++nc->stats.cellelisions; } }else{ @@ -578,6 +601,7 @@ notcurses_rasterize(notcurses* nc, unsigned char* damagemap){ if(!cell_fg_default_p(srccell)){ if(!noforeground){ cell_fg_rgb(srccell, &r, &g, &b); +//fprintf(stderr, "[%03d/%03d] %02x %02x %02x\n", y, x, r, g, b); if(nc->rstate.fgelidable && nc->rstate.lastr == r && nc->rstate.lastg == g && nc->rstate.lastb == b){ ++nc->stats.fgelisions; }else{ @@ -607,13 +631,18 @@ notcurses_rasterize(notcurses* nc, unsigned char* damagemap){ ++nc->stats.bgelisions; } } -//fprintf(stderr, "[%02d/%02d] 0x%02x 0x%02x 0x%02x\n", y, x, r, g, b); +/*if(cell_simple_p(srccell)){ +fprintf(stderr, "RAST %u [%c] to %d/%d\n", srccell->gcluster, srccell->gcluster, y, x); +}else{ +fprintf(stderr, "RAST %u [%s] to %d/%d\n", srccell->gcluster, egcpool_extended_gcluster(&nc->pool, srccell), y, x); +}*/ ret |= term_putc(out, &nc->pool, srccell); } - if((damagemask >>= 1u) == 0){ - damagemask = 0x80; + if(cell_double_wide_p(srccell)){ + ++x; ++damageidx; } + ++damageidx; } } ret |= fflush(out); @@ -632,71 +661,6 @@ notcurses_rasterize(notcurses* nc, unsigned char* damagemap){ 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; - // are we in the right half of a wide glyph? if so, we don't typically emit - // anything, *BUT* we must handle higher planes bisecting our wide glyph. - bool inright = false; - for(x = 0 ; x < nc->stdscr->lenx ; ++x){ - ncplane* p; - cell c; // no need to initialize - p = visible_cell(&c, y, x, nc->top, &depth); - // don't try to print a wide character on the last column; it'll instead - // be printed on the next line. they aren't output, but we can end up - // with one due to a resize. FIXME but...print what, exactly, instead? - if((x + 1 >= nc->stdscr->lenx && cell_double_wide_p(&c))){ - continue; // needmove will be reset as we restart the line - } - 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 - // truncating it (via lseek()), methinks - cell* prev = &nc->lastframe[fbcellidx(nc->stdscr, y, x - 1)]; - pool_release(&nc->pool, prev); - cell_init(prev); - // FIXME technically we need rerun the visible cell search...? gross - cell_load_simple(NULL, prev, ' '); - 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)); -//} - } - } - // lastframe has already been sized to match the current size, so no need - // to check whether we're within its bounds. just check the cell. - if(nc->lastframe){ - cell* oldcell = &nc->lastframe[fbcellidx(nc->stdscr, y, x)]; - if(inright){ - cell_set_wide(oldcell); - inright = false; - continue; - } - // 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)); - } - } - inright = cell_double_wide_p(&c); - } - } - if(ret){ - return ret; - } - return nc->rstate.mstrsize; -} - int notcurses_render(notcurses* nc){ struct timespec start, done; int ret; @@ -704,14 +668,13 @@ int notcurses_render(notcurses* nc){ pthread_mutex_lock(&nc->lock); pthread_cleanup_push(mutex_unlock, &nc->lock); 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); + size_t crenderlen = sizeof(struct crender) * nc->stdscr->leny * nc->stdscr->lenx; + struct crender* crender = malloc(crenderlen); + memset(crender, 0, crenderlen); + if(notcurses_render_internal(nc, crender) == 0){ + bytes = notcurses_rasterize(nc, crender); } - free(damagevec); + free(crender); int dimy, dimx; notcurses_resize(nc, &dimy, &dimx); clock_gettime(CLOCK_MONOTONIC_RAW, &done); diff --git a/tests/libav.cpp b/tests/libav.cpp index 0741f9657..307a8b8a4 100644 --- a/tests/libav.cpp +++ b/tests/libav.cpp @@ -21,6 +21,7 @@ TEST_CASE("Multimedia") { ncplane* ncp_ = notcurses_stdplane(nc_); REQUIRE(ncp_); + /* #ifdef DISABLE_FFMPEG SUBCASE("LibavDisabled"){ REQUIRE(!notcurses_canopen(nc_)); @@ -85,6 +86,7 @@ TEST_CASE("Multimedia") { ncvisual_destroy(ncv); } #endif +*/ CHECK(!notcurses_stop(nc_)); CHECK(!fclose(outfp_)); diff --git a/tests/ncplane.cpp b/tests/ncplane.cpp index 107bed28e..2e7a6ac36 100644 --- a/tests/ncplane.cpp +++ b/tests/ncplane.cpp @@ -917,6 +917,32 @@ TEST_CASE("NCPlane") { CHECK(0 == ncplane_destroy(ncp)); } + SUBCASE("RenderWides") { + CHECK(0 <= ncplane_putstr(n_, "\xe5\xbd\xa2\xe5\x85\xa8")); + cell c = CELL_TRIVIAL_INITIALIZER; + ncplane_at_yx(n_, 0, 0, &c); + CHECK(cell_double_wide_p(&c)); + ncplane_at_yx(n_, 0, 1, &c); + CHECK(cell_double_wide_p(&c)); + ncplane_at_yx(n_, 0, 2, &c); + CHECK(cell_double_wide_p(&c)); + ncplane_at_yx(n_, 0, 3, &c); + CHECK(cell_double_wide_p(&c)); + ncplane_at_yx(n_, 0, 4, &c); + CHECK(!cell_double_wide_p(&c)); + REQUIRE(0 == notcurses_render(nc_)); + notcurses_at_yx(nc_, 0, 0, &c); + CHECK(cell_double_wide_p(&c)); + notcurses_at_yx(nc_, 0, 1, &c); + CHECK(cell_double_wide_p(&c)); + notcurses_at_yx(nc_, 0, 2, &c); + CHECK(cell_double_wide_p(&c)); + notcurses_at_yx(nc_, 0, 3, &c); + CHECK(cell_double_wide_p(&c)); + notcurses_at_yx(nc_, 0, 4, &c); + CHECK(!cell_double_wide_p(&c)); + } + CHECK(0 == notcurses_stop(nc_)); CHECK(0 == fclose(outfp_));