break apart render/rasterize #155

This commit is contained in:
nick black 2020-01-12 18:00:37 -05:00
parent d9b721971a
commit 56a54b5441
No known key found for this signature in database
GPG Key ID: 5F43400C21CBFACC
3 changed files with 192 additions and 154 deletions

View File

@ -1491,13 +1491,6 @@ cell_noforeground_p(const cell* c){
return cell_simple_p(c) && isspace(c->gcluster); 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 static inline int
cell_load_simple(struct ncplane* n, cell* c, char ch){ cell_load_simple(struct ncplane* n, cell* c, char ch){
cell_release(n, c); cell_release(n, c);

View File

@ -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 :/ // no CLOCK_MONOTONIC_RAW on FreeBSD as of 12.0 :/
#ifndef CLOCK_MONOTONIC_RAW #ifndef CLOCK_MONOTONIC_RAW
#define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC #define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC

View File

@ -11,6 +11,7 @@ mutex_unlock(void* vlock){
static int static int
blocking_write(int fd, const char* buf, size_t buflen){ blocking_write(int fd, const char* buf, size_t buflen){
//fprintf(stderr, "writing %zu to %d...\n", buflen, fd);
size_t written = 0; size_t written = 0;
do{ do{
ssize_t w = write(fd, buf + written, buflen - written); 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. // 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 // it's probably better to sample the darker regions rather than cover so much
// chroma, but whatever....FIXME // chroma, but whatever....FIXME
static inline int /*static inline int
prep_optimized_palette(notcurses* nc, FILE* out __attribute__ ((unused))){ prep_optimized_palette(notcurses* nc, FILE* out __attribute__ ((unused))){
if(nc->RGBflag){ if(nc->RGBflag){
return 0; // DirectColor, no need to write palette return 0; // DirectColor, no need to write palette
@ -92,7 +93,7 @@ prep_optimized_palette(notcurses* nc, FILE* out __attribute__ ((unused))){
} }
// FIXME // FIXME
return 0; return 0;
} }*/
// reshape the shadow framebuffer to match the stdplane's dimensions, throwing // reshape the shadow framebuffer to match the stdplane's dimensions, throwing
// away the old one. // 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 // number of columns occupied by this EGC (only an approximation; it's actually
// a property of the font being used). // a property of the font being used).
static int 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(cell_simple_p(c)){
if(c->gcluster == 0 || iscntrl(c->gcluster)){ if(c->gcluster == 0 || iscntrl(c->gcluster)){
// fprintf(stderr, "[ ]\n"); // fprintf(stderr, "[ ]\n");
@ -266,7 +267,7 @@ term_putc(FILE* out, const ncplane* n, const cell* c){
} }
} }
}else{ }else{
const char* ext = extended_gcluster(n, c); const char* ext = egcpool_extended_gcluster(e, c);
// fprintf(stderr, "[%s]\n", ext); // fprintf(stderr, "[%s]\n", ext);
#ifdef __USE_GNU #ifdef __USE_GNU
if(fputs_unlocked(ext, out) < 0){ // FIXME check for short write? if(fputs_unlocked(ext, out) < 0){ // FIXME check for short write?
@ -483,93 +484,52 @@ cellcmp_and_dupfar(egcpool* dampool, cell* damcell, const ncplane* srcplane,
return 1; 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 static inline int
notcurses_render_internal(notcurses* nc){ notcurses_rasterize(notcurses* nc, unsigned char* damagemap){
FILE* out = nc->rstate.mstreamfp;
int ret = 0; int ret = 0;
int y, x; int y, x;
FILE* out = nc->rstate.mstreamfp;
fseeko(out, 0, SEEK_SET); 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. // 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 // we explicitly move the cursor at the beginning of each output line, so no
// need to home it expliticly. // 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){ for(y = 0 ; y < nc->stdscr->leny ; ++y){
// how many characters have we elided? it's not worthwhile to invoke a // 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 // cursor movement with cup if we only elided one or two. set to INT_MAX
// whenever we're on a new line. // whenever we're on a new line.
int needmove = INT_MAX; int needmove = INT_MAX;
// 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){ for(x = 0 ; x < nc->stdscr->lenx ; ++x){
unsigned r, g, b, br, bg, bb; unsigned r, g, b, br, bg, bb;
ncplane* p; const cell* srccell = &nc->lastframe[y * nc->lfdimx + x];
cell c; // no need to initialize // cell c;
p = visible_cell(&c, y, x, nc->top, &depth); // memcpy(c, srccell, sizeof(*c)); // unsafe copy of gcluster
// don't try to print a wide character on the last column; it'll instead //fprintf(stderr, "COPYING: %d from %p\n", c->gcluster, &nc->pool);
// be printed on the next line. they aren't output, but we can end up // const char* egc = pool_egc_copy(&nc->pool, srccell);
// with one due to a resize. FIXME but...print what, exactly, instead? // c->gcluster = 0; // otherwise cell_release() will blow up
if((x + 1 >= nc->stdscr->lenx && cell_double_wide_p(&c))){ //fprintf(stderr, "idx: %zu word: 0x%02x\n", damageidx, damagemap[damageidx]);
continue; // needmove will be reset as we restart the line if((damagemap[damageidx] & damagemask) == 0){
}
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
// 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, ' ');
// 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
// 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, 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 // no need to emit a cell; what we rendered appears to already be
// here. no updates are performed to elision state nor lastframe. // here. no updates are performed to elision state nor lastframe.
++nc->stats.cellelisions; ++nc->stats.cellelisions;
if(needmove < INT_MAX){ if(needmove < INT_MAX){
++needmove; ++needmove;
} }
if(cell_double_wide_p(&c)){ if(cell_double_wide_p(srccell)){
if(needmove < INT_MAX){ if(needmove < INT_MAX){
++needmove; ++needmove;
} }
++nc->stats.cellelisions; ++nc->stats.cellelisions;
inright = !inright; }
}else{ }else{
inright = false;
}
continue;
}
}
}
++nc->stats.cellemissions; ++nc->stats.cellemissions;
if(needmove == 1 && nc->cuf1){ if(needmove == 1 && nc->cuf1){
ret |= term_emit("cuf1", tiparm(nc->cuf1), out, false); ret |= term_emit("cuf1", tiparm(nc->cuf1), out, false);
@ -580,7 +540,7 @@ notcurses_render_internal(notcurses* nc){
// set the style. this can change the color back to the default; if it // set the style. this can change the color back to the default; if it
// does, we need update our elision possibilities. // does, we need update our elision possibilities.
bool normalized; bool normalized;
term_setstyles(nc, out, &nc->rstate.curattr, &c, &normalized); ret |= term_setstyles(nc, out, &nc->rstate.curattr, srccell, &normalized);
if(normalized){ if(normalized){
nc->rstate.defaultelidable = true; nc->rstate.defaultelidable = true;
nc->rstate.bgelidable = false; nc->rstate.bgelidable = false;
@ -596,12 +556,12 @@ notcurses_render_internal(notcurses* nc){
// * we are a partial glyph, and the previous was default on both, or // * 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-foreground glyph, and the previous was default background, or
// * we are a no-background glyph, and the previous was default foreground // * we are a no-background glyph, and the previous was default foreground
bool noforeground = cell_noforeground_p(&c); bool noforeground = cell_noforeground_p(srccell);
bool nobackground = cell_nobackground_p(p, &c); bool nobackground = cell_nobackground_p(&nc->pool, srccell);
if((!noforeground && cell_fg_default_p(&c)) || (!nobackground && cell_bg_default_p(&c))){ if((!noforeground && cell_fg_default_p(srccell)) || (!nobackground && cell_bg_default_p(srccell))){
if(!nc->rstate.defaultelidable){ if(!nc->rstate.defaultelidable){
++nc->stats.defaultemissions; ++nc->stats.defaultemissions;
term_emit("op", nc->op, out, false); ret |= term_emit("op", nc->op, out, false);
}else{ }else{
++nc->stats.defaultelisions; ++nc->stats.defaultelisions;
} }
@ -615,13 +575,13 @@ notcurses_render_internal(notcurses* nc){
// foreground set iff either: // foreground set iff either:
// * the previous was non-default, and matches what we have now, or // * the previous was non-default, and matches what we have now, or
// * we are a no-foreground glyph (iswspace() is true) // * we are a no-foreground glyph (iswspace() is true)
if(!cell_fg_default_p(&c)){ if(!cell_fg_default_p(srccell)){
if(!noforeground){ if(!noforeground){
cell_fg_rgb(&c, &r, &g, &b); cell_fg_rgb(srccell, &r, &g, &b);
if(nc->rstate.fgelidable && nc->rstate.lastr == r && nc->rstate.lastg == g && nc->rstate.lastb == b){ if(nc->rstate.fgelidable && nc->rstate.lastr == r && nc->rstate.lastg == g && nc->rstate.lastb == b){
++nc->stats.fgelisions; ++nc->stats.fgelisions;
}else{ }else{
term_fg_rgb8(nc, out, r, g, b); ret |= term_fg_rgb8(nc, out, r, g, b);
++nc->stats.fgemissions; ++nc->stats.fgemissions;
nc->rstate.fgelidable = true; nc->rstate.fgelidable = true;
} }
@ -631,13 +591,13 @@ notcurses_render_internal(notcurses* nc){
++nc->stats.fgelisions; ++nc->stats.fgelisions;
} }
} }
if(!cell_bg_default_p(&c)){ if(!cell_bg_default_p(srccell)){
if(!nobackground){ if(!nobackground){
cell_bg_rgb(&c, &br, &bg, &bb); cell_bg_rgb(srccell, &br, &bg, &bb);
if(nc->rstate.bgelidable && nc->rstate.lastbr == br && nc->rstate.lastbg == bg && nc->rstate.lastbb == bb){ if(nc->rstate.bgelidable && nc->rstate.lastbr == br && nc->rstate.lastbg == bg && nc->rstate.lastbb == bb){
++nc->stats.bgelisions; ++nc->stats.bgelisions;
}else{ }else{
term_bg_rgb8(nc, out, br, bg, bb); ret |= term_bg_rgb8(nc, out, br, bg, bb);
++nc->stats.bgemissions; ++nc->stats.bgemissions;
nc->rstate.bgelidable = true; nc->rstate.bgelidable = true;
} }
@ -647,9 +607,13 @@ notcurses_render_internal(notcurses* nc){
++nc->stats.bgelisions; ++nc->stats.bgelisions;
} }
} }
//fprintf(stderr, "[%02d/%02d] 0x%02x 0x%02x 0x%02x %p\n", y, x, r, g, b, p); //fprintf(stderr, "[%02d/%02d] 0x%02x 0x%02x 0x%02x\n", y, x, r, g, b);
term_putc(out, p, &c); ret |= term_putc(out, &nc->pool, srccell);
inright = cell_double_wide_p(&c); }
if((damagemask >>= 1u) == 0){
damagemask = 0x80;
++damageidx;
}
} }
} }
ret |= fflush(out); ret |= fflush(out);
@ -662,6 +626,71 @@ notcurses_render_internal(notcurses* nc){
if(nc->renderfp){ if(nc->renderfp){
fprintf(nc->renderfp, "%s\n", nc->rstate.mstream); 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;
// 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){ if(ret){
return ret; return ret;
} }
@ -674,7 +703,15 @@ int notcurses_render(notcurses* nc){
clock_gettime(CLOCK_MONOTONIC_RAW, &start); clock_gettime(CLOCK_MONOTONIC_RAW, &start);
pthread_mutex_lock(&nc->lock); pthread_mutex_lock(&nc->lock);
pthread_cleanup_push(mutex_unlock, &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; int dimy, dimx;
notcurses_resize(nc, &dimy, &dimx); notcurses_resize(nc, &dimy, &dimx);
clock_gettime(CLOCK_MONOTONIC_RAW, &done); clock_gettime(CLOCK_MONOTONIC_RAW, &done);