diff --git a/NEWS.md b/NEWS.md index 757e4b239..503097943 100644 --- a/NEWS.md +++ b/NEWS.md @@ -23,7 +23,6 @@ rearrangements of Notcurses. * Add new function `ncpile_render()`, which renders the pile containing the specified plane to the specified buffer. Add new function `ncpile_rasterize()` to rasterize the specified buffer to output. - * `notcurses_render_to_buffer()` has been deprecated. Use `ncpile_render()`. * 2.0.7 (2020-11-21) * The `horiz` union of `ncplane_options` has been discarded; the `int x` diff --git a/include/ncpp/NotCurses.hh b/include/ncpp/NotCurses.hh index 59894c24d..ebd972e8d 100644 --- a/include/ncpp/NotCurses.hh +++ b/include/ncpp/NotCurses.hh @@ -195,6 +195,11 @@ namespace ncpp return error_guard (notcurses_render (nc), -1); } + bool render_to_buffer (char** buf, size_t* buflen) const NOEXCEPT_MAYBE + { + return error_guard (notcurses_render_to_buffer (nc, buf, buflen), -1); + } + bool render_to_file (FILE* fp) const NOEXCEPT_MAYBE { return error_guard (notcurses_render_to_file (nc, fp), -1); diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index 70b58bf30..2cca02ff7 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -857,8 +857,7 @@ API int notcurses_render(struct notcurses* nc); // do not write the resulting buffer out to the terminal. Using this function, // the user can control the writeout process, and render a second frame while // writing another. The returned buffer must be freed by the caller. -API int notcurses_render_to_buffer(struct notcurses* nc, char** buf, size_t* buflen) - __attribute__ ((deprecated)); +API int notcurses_render_to_buffer(struct notcurses* nc, char** buf, size_t* buflen); // Write the last rendered frame, in its entirety, to 'fp'. If // notcurses_render() has not yet been called, nothing will be written. diff --git a/src/lib/internal.h b/src/lib/internal.h index 8950fb0e6..2d683c719 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -96,11 +96,11 @@ typedef struct ncplane { // current presentation state of the terminal. it is carried across render // instances. initialize everything to 0 on a terminal reset / startup. typedef struct renderstate { - // we assemble the encoded output in a POSIX memstream, and keep it around - // between uses. this could be a problem if it ever tremendously spiked, but - // that's a highly unlikely situation. - char* mstream; // buffer for rendering memstream, see open_memstream(3) - FILE* mstreamfp;// FILE* for rendering memstream + // we assemble the encoded (rasterized) output in a POSIX memstream, and keep + // it around between uses. this could be a problem if it ever tremendously + // spiked, but that's a highly unlikely situation. + char* mstream; // buffer for rasterizing memstream, see open_memstream(3) + FILE* mstreamfp;// FILE* for rasterizing memstream size_t mstrsize;// size of rendering memstream // the current cursor position. this is independent of whether the cursor is @@ -298,20 +298,13 @@ typedef struct ncdirect { uint64_t flags; // copied in ncdirect_init() from param } ncdirect; -// A rendering context/result, suitable for reuse across many rendering -// operations. A pile's planes are rendered down into a single virtual plane, -// which can be (relative to the last rendered plane, common across the -// notcurses context) rasterized to the actual terminal. -typedef struct ncrender { - char* buf; - size_t buflen; -} ncrender; - typedef struct ncpile { - ncplane* top; // topmost plane, never NULL - ncplane* bottom; // bottommost plane, never NULL - struct notcurses* nc; // notcurses context + ncplane* top; // topmost plane, never NULL + ncplane* bottom; // bottommost plane, never NULL + struct notcurses* nc; // notcurses context struct ncpile *prev, *next; // circular list + size_t crenderlen; // size of crender array in bytes + struct crender* crender; // crender array } ncpile; // the standard pile can be reached through ->stdplane. diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index ce86e3c44..b660c7f9b 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -271,6 +271,7 @@ ncpile_destroy(ncpile* pile){ if(pile){ pile->prev->next = pile->next; pile->next->prev = pile->prev; + free(pile->crender); free(pile); } } @@ -315,6 +316,8 @@ make_ncpile(notcurses* nc, ncplane* n){ n->pile = ret; n->above = NULL; n->below = NULL; + ret->crenderlen = 0; + ret->crender = NULL; } return ret; } diff --git a/src/lib/render.c b/src/lib/render.c index 57a7cbe64..574eba325 100644 --- a/src/lib/render.c +++ b/src/lib/render.c @@ -152,11 +152,10 @@ int cell_duplicate(ncplane* n, cell* targ, const cell* c){ return 0; } -// Extracellular state for a cell during the render process. This array is -// passed along to rasterization, which uses only the 'damaged' bools. There -// is one crender per rendered cell, and they are initialized to all zeroes. +// Extracellular state for a cell during the render process. There is one +// crender per rendered cell, and they are initialized to all zeroes. struct crender { - const ncplane *p; + const ncplane *p; // source of glyph for this cell cell c; unsigned fgblends; unsigned bgblends; @@ -366,7 +365,7 @@ paint(const ncplane* p, struct crender* rvec, int dstleny, int dstlenx, // it's not a pure memset(), because CELL_ALPHA_OPAQUE is the zero value, and // we need CELL_ALPHA_TRANSPARENT -static void +static inline void init_rvec(struct crender* rvec, int totalcells){ memset(rvec, 0, sizeof(*rvec) * totalcells); for(int t = 0 ; t < totalcells ; ++t){ @@ -709,12 +708,12 @@ update_palette(notcurses* nc, FILE* out){ return 0; } -// sync the cursor to the specified location with as little overhead as -// possible (with nothing, if already at the right location). +// sync the drawing position to the specified location with as little overhead +// as possible (with nothing, if already at the right location). // FIXME fall back to synthesized moves in the absence of capabilities (i.e. // textronix lacks cup; fake it with horiz+vert moves) static inline int -stage_cursor(notcurses* nc, FILE* out, int y, int x){ +goto_location(notcurses* nc, FILE* out, int y, int x){ int ret = 0; if(nc->rstate.y == y){ // only need move x const int xdiff = x - nc->rstate.x; @@ -779,7 +778,7 @@ notcurses_rasterize_inner(notcurses* nc, const struct crender* rvec, FILE* out){ } }else{ ++nc->stats.cellemissions; - if(stage_cursor(nc, out, y, x)){ + if(goto_location(nc, out, y, x)){ return -1; } // set the style. this can change the color back to the default; if it @@ -1038,7 +1037,7 @@ int notcurses_render_to_file(notcurses* nc, FILE* fp){ // 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 int +static void ncpile_render_internal(ncplane* n, struct crender* rvec, int leny, int lenx, int absy, int absx){ ncplane* p = ncplane_pile(n)->top; @@ -1046,63 +1045,78 @@ ncpile_render_internal(ncplane* n, struct crender* rvec, int leny, int lenx, paint(p, rvec, leny, lenx, absy, absx); p = p->below; } - return 0; } -int ncpile_render(ncplane* n){ - struct timespec start, rasterdone; +int ncpile_rasterize(ncplane* n){ + struct timespec start, writedone; clock_gettime(CLOCK_MONOTONIC, &start); int dimy, dimx; + const struct ncpile* pile = ncplane_pile(n); notcurses_resize(ncplane_notcurses(n), &dimy, &dimx); - int bytes = -1; - const size_t crenderlen = sizeof(struct crender) * dimy * dimx; - struct crender* crender = malloc(crenderlen); - init_rvec(crender, crenderlen / sizeof(struct crender)); - if(ncpile_render_internal(n, crender, dimy, dimx, - notcurses_stdplane(ncplane_notcurses(n))->absy, - notcurses_stdplane(ncplane_notcurses(n))->absx) == 0){ - clock_gettime(CLOCK_MONOTONIC, &rasterdone); - update_render_stats(&rasterdone, &start, &ncplane_notcurses(n)->stats); - // FIXME extract and move to ncpile_rasterize - postpaint(ncplane_notcurses(n)->lastframe, dimy, dimx, crender, - &ncplane_notcurses(n)->pool); - bytes = notcurses_rasterize(ncplane_notcurses(n), crender, - ncplane_notcurses(n)->rstate.mstreamfp); - } + postpaint(ncplane_notcurses(n)->lastframe, dimy, dimx, pile->crender, + &ncplane_notcurses(n)->pool); + int bytes = notcurses_rasterize(ncplane_notcurses(n), pile->crender, + ncplane_notcurses(n)->rstate.mstreamfp); // accepts -1 as an indication of failure update_render_bytes(&ncplane_notcurses(n)->stats, bytes); - free(crender); if(bytes < 0){ return -1; } - struct timespec writedone; clock_gettime(CLOCK_MONOTONIC, &writedone); - update_write_stats(&writedone, &rasterdone, &ncplane_notcurses(n)->stats); + update_write_stats(&writedone, &start, &ncplane_notcurses(n)->stats); + return 0; +} + +// ensure the crender vector of 'n' is sufficiently large for 'dimy'x'dimx' +static int +engorge_crender_vector(ncpile* n, int dimy, int dimx){ + const size_t crenderlen = sizeof(struct crender) * dimy * dimx; + if(crenderlen > n->crenderlen){ + struct crender* tmp = realloc(n->crender, crenderlen); + if(tmp == NULL){ + return -1; + } + n->crender = tmp; + n->crenderlen = crenderlen; + } + init_rvec(n->crender, crenderlen / sizeof(struct crender)); + return 0; +} + +int ncpile_render(ncplane* n){ + struct timespec start, renderdone; + clock_gettime(CLOCK_MONOTONIC, &start); + int dimy, dimx; + // render against our current notion of screen geometry + ncplane_dim_yx(notcurses_stdplane(ncplane_notcurses(n)), &dimy, &dimx); + if(engorge_crender_vector(ncplane_pile(n), dimy, dimx)){ + return -1; + } + ncpile_render_internal(n, ncplane_pile(n)->crender, dimy, dimx, + notcurses_stdplane(ncplane_notcurses(n))->absy, + notcurses_stdplane(ncplane_notcurses(n))->absx); + clock_gettime(CLOCK_MONOTONIC, &renderdone); + update_render_stats(&renderdone, &start, &ncplane_notcurses(n)->stats); return 0; } int notcurses_render(notcurses* nc){ - return ncpile_render(notcurses_stdplane(nc)); + struct ncplane* stdn = notcurses_stdplane(nc); + if(ncpile_render(stdn)){ + return -1; + } + return(ncpile_rasterize(stdn)); } // for now, we just run the top half of notcurses_render(), and copy out the // memstream from within rstate. we want to allocate our own here, and return // it, to avoid the copy, but we need feed the params through to do so FIXME. int notcurses_render_to_buffer(notcurses* nc, char** buf, size_t* buflen){ - struct timespec start, rasterdone; - clock_gettime(CLOCK_MONOTONIC, &start); - int dimy, dimx; - notcurses_resize(nc, &dimy, &dimx); - int bytes = -1; - const size_t crenderlen = sizeof(struct crender) * dimy * dimx; - struct crender* crender = malloc(crenderlen); - init_rvec(crender, crenderlen / sizeof(struct crender)); - if(ncpile_render_internal(nc->stdplane, crender, dimy, dimx, nc->stdplane->absy, nc->stdplane->absx) == 0){ - clock_gettime(CLOCK_MONOTONIC, &rasterdone); - update_render_stats(&rasterdone, &start, &nc->stats); - postpaint(nc->lastframe, dimy, dimx, crender, &nc->pool); - bytes = notcurses_rasterize_inner(nc, crender, nc->rstate.mstreamfp); + struct ncplane* stdn = notcurses_stdplane(nc); + if(ncpile_render(stdn)){ + return -1; } + int bytes = notcurses_rasterize_inner(nc, ncplane_pile(stdn)->crender, nc->rstate.mstreamfp); update_render_bytes(&nc->stats, bytes); if(bytes < 0){ return -1; @@ -1188,7 +1202,7 @@ int notcurses_cursor_enable(notcurses* nc, int y, int x){ if(nc->ttyfd < 0 || !nc->tcache.cnorm){ return -1; } - if(stage_cursor(nc, nc->ttyfp, y + nc->stdplane->absy, x + nc->stdplane->absx)){ + if(goto_location(nc, nc->ttyfp, y + nc->stdplane->absy, x + nc->stdplane->absx)){ return -1; } // if we were already positive, we're already visible, no need to write cnorm