diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index 6dbf00f7d..f736daca5 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -1749,6 +1749,28 @@ cell_simple_p(const cell* c){ // 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); +// Returns true if the two cells are distinct EGCs, attributes, or channels. +// The actual egcpool index needn't be the same--indeed, the planes needn't even +// be the same. Only the expanded EGC must be equal. The EGC must be bit-equal; +// it would probably be better to test whether they're Unicode-equal FIXME. +static inline bool +cellcmp(const struct ncplane* n1, const cell* RESTRICT c1, + const struct ncplane* n2, const cell* RESTRICT c2){ + if(c1->attrword != c2->attrword){ + return true; + } + if(c1->channels != c2->channels){ + return true; + } + if(cell_simple_p(c1) && cell_simple_p(c2)){ + return c1->gcluster != c2->gcluster; + } + if(cell_simple_p(c1) || cell_simple_p(c2)){ + return true; + } + return strcmp(cell_extended_gcluster(n1, c1), cell_extended_gcluster(n2, c2)); +} + // 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 diff --git a/src/lib/render.c b/src/lib/render.c index 487ac43c4..c814f39eb 100644 --- a/src/lib/render.c +++ b/src/lib/render.c @@ -205,9 +205,10 @@ lock_in_highcontrast(cell* targc, struct crender* crender){ // 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. +// 'damagevec' bitmap is updated with a 1. 'pool' is typically nc->pool, but can +// be whatever's backing fb. static int -paint(notcurses* nc, ncplane* p, struct crender* rvec, cell* fb){ +paint(notcurses* nc, ncplane* p, cell* lastframe, struct crender* rvec, cell* fb, egcpool* pool){ int y, x, dimy, dimx, offy, offx; // don't use ncplane_dim_yx()/ncplane_yx() here, lest we deadlock dimy = p->leny; @@ -312,14 +313,14 @@ paint(notcurses* nc, ncplane* p, struct crender* rvec, cell* fb){ // which were already locked in were skipped at the top of the loop)? if(cell_locked_p(targc)){ lock_in_highcontrast(targc, crender); - cell* prevcell = &nc->lastframe[fbcellidx(absy, nc->lfdimx, absx)]; + cell* prevcell = &lastframe[fbcellidx(absy, nc->lfdimx, absx)]; /*if(cell_simple_p(targc)){ fprintf(stderr, "WROTE %u [%c] to %d/%d (%d/%d)\n", targc->gcluster, prevcell->gcluster, y, x, absy, absx); }else{ fprintf(stderr, "WROTE %u [%s] to %d/%d (%d/%d)\n", targc->gcluster, extended_gcluster(crender->p, targc), y, x, absy, absx); } fprintf(stderr, "POOL: %p NC: %p SRC: %p\n", nc->pool.pool, nc, crender->p);*/ - if(cellcmp_and_dupfar(&nc->pool, prevcell, crender->p, targc)){ + if(cellcmp_and_dupfar(pool, prevcell, crender->p, targc)){ crender->damaged = true; if(cell_double_wide_p(targc)){ ncplane* tmpp = crender->p; @@ -331,7 +332,7 @@ fprintf(stderr, "POOL: %p NC: %p SRC: %p\n", nc->pool.pool, nc, crender->p);*/ targc->gcluster = 0; targc->channels = targc[-1].channels; targc->attrword = targc[-1].attrword; - if(cellcmp_and_dupfar(&nc->pool, prevcell, crender->p, targc)){ + if(cellcmp_and_dupfar(pool, prevcell, crender->p, targc)){ crender->damaged = true; } } @@ -342,12 +343,62 @@ fprintf(stderr, "POOL: %p NC: %p SRC: %p\n", nc->pool.pool, nc, crender->p);*/ return 0; } -static int -ncplane_mergedown(ncplane* restrict src, ncplane* restrict dst){ - if(paint(nc, src, rvec, fb)){ +static void +init_fb(cell* fb, int dimy, int 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); + } + } +} + +static void +postpaint(cell* fb, cell* lastframe, int dimy, int dimx, + struct crender* rvec, egcpool* pool){ + 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)){ + struct crender* crender = &rvec[fbcellidx(y, dimx, x)]; + lock_in_highcontrast(targc, crender); + cell* prevcell = &lastframe[fbcellidx(y, dimx, x)]; + if(targc->gcluster == 0){ + targc->gcluster = ' '; + } + if(cellcmp_and_dupfar(pool, prevcell, crender->p, targc)){ + crender->damaged = true; + } + } + } + } +} + +// FIXME need handle a dst that isn't the standard plane! paint() will only +// paint within the real viewport currently. +int ncplane_mergedown(ncplane* restrict src, ncplane* restrict dst){ + notcurses* nc = src->nc; + int dimy, dimx; + ncplane_dim_yx(dst, &dimy, &dimx); + cell* fb = malloc(sizeof(*fb) * dimy * dimx); + const size_t crenderlen = sizeof(struct crender) * dimy * dimx; + struct crender* rvec = malloc(crenderlen); + memset(rvec, 0, crenderlen); + init_fb(fb, dimy, dimx); + if(paint(nc, src, dst->fb, rvec, fb, &dst->pool) || paint(nc, dst, dst->fb, rvec, fb, &dst->pool)){ + free(rvec); + free(fb); return -1; } - // if src is below dst, dst + postpaint(fb, dst->fb, dimy, dimx, rvec, &dst->pool); + free(dst->fb); + dst->fb = fb; + free(rvec); + return 0; } // We execute the painter's algorithm, starting from our topmost plane. The @@ -360,43 +411,19 @@ notcurses_render_internal(notcurses* nc, struct crender* rvec){ if(reshape_shadow_fb(nc)){ return -1; } - // don't use ncplane_dim_yx()/ncplane_yx() here, lest we deadlock - int dimy = nc->stdscr->leny; - int dimx = nc->stdscr->lenx; + int dimy, dimx; + ncplane_dim_yx(nc->stdscr, &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); - } - } + init_fb(fb, dimy, dimx); ncplane* p = nc->top; while(p){ - if(paint(nc, p, rvec, fb)){ + if(paint(nc, p, nc->lastframe, rvec, fb, &nc->pool)){ + free(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)){ - struct crender* crender = &rvec[fbcellidx(y, dimx, x)]; - lock_in_highcontrast(targc, crender); - cell* prevcell = &nc->lastframe[fbcellidx(y, dimx, x)]; - if(targc->gcluster == 0){ - targc->gcluster = ' '; - } - if(cellcmp_and_dupfar(&nc->pool, prevcell, crender->p, targc)){ - crender->damaged = true; - } - } - } - } + postpaint(fb, nc->lastframe, dimy, dimx, rvec, &nc->pool); free(fb); return 0; } @@ -979,7 +1006,7 @@ int notcurses_render(notcurses* nc){ int ret; clock_gettime(CLOCK_MONOTONIC, &start); int bytes = -1; - size_t crenderlen = sizeof(struct crender) * nc->stdscr->leny * nc->stdscr->lenx; + const 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){ diff --git a/tests/fills.cpp b/tests/fills.cpp index 4ade65b56..945829be8 100644 --- a/tests/fills.cpp +++ b/tests/fills.cpp @@ -311,6 +311,40 @@ TEST_CASE("Fills") { CHECK(0 == notcurses_render(nc_)); } + SUBCASE("MergeDown") { + auto p1 = ncplane_new(nc_, 1, 10, 0, 0, nullptr); + REQUIRE(p1); + // make sure glyphs replace nulls + CHECK(0 < ncplane_putstr(p1, "0123456789")); + CHECK(0 == ncplane_mergedown(p1, n_)); + cell cbase = CELL_TRIVIAL_INITIALIZER; + cell cp = CELL_TRIVIAL_INITIALIZER; + for(int i = 0 ; i < 10 ; ++i){ + CHECK(0 < ncplane_at_yx(n_, 0, i, &cbase)); + CHECK(0 < ncplane_at_yx(p1, 0, i, &cp)); + CHECK(0 == cellcmp(n_, &cbase, p1, &cp)); + } + CHECK(0 == ncplane_cursor_move_yx(p1, 0, 0)); + // make sure glyphs replace glyps + CHECK(0 < ncplane_putstr(p1, "9876543210")); + CHECK(0 == ncplane_mergedown(p1, n_)); + for(int i = 0 ; i < 10 ; ++i){ + CHECK(0 < ncplane_at_yx(n_, 0, i, &cbase)); + CHECK(0 < ncplane_at_yx(p1, 0, i, &cp)); + CHECK(0 == cellcmp(n_, &cbase, p1, &cp)); + } + // make sure nulls do not replace glyphs + auto p2 = ncplane_new(nc_, 1, 10, 0, 0, nullptr); + CHECK(0 == ncplane_mergedown(p2, n_)); + ncplane_destroy(p2); + for(int i = 0 ; i < 10 ; ++i){ + CHECK(0 < ncplane_at_yx(n_, 0, i, &cbase)); + CHECK(0 < ncplane_at_yx(p1, 0, i, &cp)); + CHECK(0 == cellcmp(n_, &cbase, p1, &cp)); + } + ncplane_destroy(p1); + } + CHECK(0 == notcurses_stop(nc_)); CHECK(0 == fclose(outfp_));