From 5b322add56834d4cf5bddb86b5e7adfe43b6270f Mon Sep 17 00:00:00 2001 From: nick black Date: Fri, 27 Dec 2019 19:49:47 -0500 Subject: [PATCH] glyph-based background spec elision #131 When we emit a glyph that has no background pixels (i.e. the U+2588 FULL BLOCK glyph), there's no need to emit a background color change. Eagle demo currently has hand-coded elision. Results from 80x70 runs using the `-c` parameter: No optimization: 12.63MiB Hand-optimized: 12.48MiB New scheme, no hand-coded optimization: 12.45MiB w00t! --- include/notcurses.h | 28 ++++++++++++++++++++++------ src/demo/eagle.c | 2 -- src/lib/render.c | 21 ++++++++++++++++----- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/include/notcurses.h b/include/notcurses.h index 0033c40f2..89b233654 100644 --- a/include/notcurses.h +++ b/include/notcurses.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -1317,18 +1318,37 @@ cell_set_bg_alpha(cell* c, int alpha){ return channels_set_bg_alpha(&c->channels, alpha); } -// does the cell contain an East Asian Wide codepoint? +// Does the cell contain an East Asian Wide codepoint? static inline bool cell_double_wide_p(const cell* c){ return (c->channels & CELL_WIDEASIAN_MASK); } -// is the cell simple (a lone ASCII character, encoded as such)? +// Is the cell simple (a lone ASCII character, encoded as such)? static inline bool cell_simple_p(const cell* c){ return c->gcluster < 0x80; } +// return a pointer to the NUL-terminated EGC referenced by 'c'. this pointer +// is invalidated by any further operation on the plane 'n', so...watch out! +API const char* cell_extended_gcluster(const struct ncplane* n, const cell* c); + +// True if the cell does not generate foreground pixels (i.e., the cell is +// entirely whitespace or special characters). +// FIXME do this at cell prep time and set a bit in the channels +static inline bool +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); @@ -1347,10 +1367,6 @@ cell_egc_idx(const cell* c){ return c->gcluster - 0x80; } -// return a pointer to the NUL-terminated EGC referenced by 'c'. this pointer -// is invalidated by any further operation on the plane 'n', so...watch out! -API const char* cell_extended_gcluster(const struct ncplane* n, const cell* c); - // load up six cells with the EGCs necessary to draw a box. returns 0 on // success, -1 on error. on error, any cells this function might // have loaded before the error are cell_release()d. There must be at least diff --git a/src/demo/eagle.c b/src/demo/eagle.c index 8f3cc79f1..8dd21353e 100644 --- a/src/demo/eagle.c +++ b/src/demo/eagle.c @@ -121,8 +121,6 @@ draw_eagle(struct ncplane* n, const char* sprite){ size_t s; int sbytes; uint64_t channels = 0; - // optimization so we can elide more color changes, see README's "#perf" - channels_set_bg_rgb(&channels, 0x00, 0x00, 0x00); ncplane_cursor_move_yx(n, 0, 0); for(s = 0 ; sprite[s] ; ++s){ switch(sprite[s]){ diff --git a/src/lib/render.c b/src/lib/render.c index d87b14998..a45fe166e 100644 --- a/src/lib/render.c +++ b/src/lib/render.c @@ -534,8 +534,16 @@ notcurses_render_internal(notcurses* nc){ // escapes ourselves, if either is set to default, we first send op, and // then a turnon for whichever aren't default. - // we can elide the default set iff the previous used both defaults - if(cell_fg_default_p(&c) || cell_bg_default_p(&c)){ + // 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 + + // FIXME move these into the cell bits + 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); @@ -548,8 +556,11 @@ notcurses_render_internal(notcurses* nc){ nc->rstate.bgelidable = false; } - // we can elide the foreground set iff the previous used fg and matched - if(!cell_fg_default_p(&c)){ + // 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(/*!noforeground &&*/ !cell_fg_default_p(&c)){ cell_get_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; @@ -561,7 +572,7 @@ notcurses_render_internal(notcurses* nc){ nc->rstate.lastr = r; nc->rstate.lastg = g; nc->rstate.lastb = b; nc->rstate.defaultelidable = false; } - if(!cell_bg_default_p(&c)){ + if(!nobackground && !cell_bg_default_p(&c)){ cell_get_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;