Painter's algorithm #255 (#274)

* notcursesI.avi 30 -> 60fps
* painter's algorithm works #255
pull/278/head
Nick Black 5 years ago committed by GitHub
parent 7c687faea0
commit 7ec022d067
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

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

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

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

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

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

Loading…
Cancel
Save