implement ncpile_{render, rasterize}() 🦀🦀 #1135

pull/1142/head
nick black 4 years ago committed by Nick Black
parent 7a21574798
commit fd97aa844c

@ -23,7 +23,6 @@ rearrangements of Notcurses.
* Add new function `ncpile_render()`, which renders the pile containing the * Add new function `ncpile_render()`, which renders the pile containing the
specified plane to the specified buffer. Add new function specified plane to the specified buffer. Add new function
`ncpile_rasterize()` to rasterize the specified buffer to output. `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) * 2.0.7 (2020-11-21)
* The `horiz` union of `ncplane_options` has been discarded; the `int x` * The `horiz` union of `ncplane_options` has been discarded; the `int x`

@ -195,6 +195,11 @@ namespace ncpp
return error_guard (notcurses_render (nc), -1); 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 bool render_to_file (FILE* fp) const NOEXCEPT_MAYBE
{ {
return error_guard (notcurses_render_to_file (nc, fp), -1); return error_guard (notcurses_render_to_file (nc, fp), -1);

@ -857,8 +857,7 @@ API int notcurses_render(struct notcurses* nc);
// do not write the resulting buffer out to the terminal. Using this function, // 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 // the user can control the writeout process, and render a second frame while
// writing another. The returned buffer must be freed by the caller. // 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) API int notcurses_render_to_buffer(struct notcurses* nc, char** buf, size_t* buflen);
__attribute__ ((deprecated));
// Write the last rendered frame, in its entirety, to 'fp'. If // Write the last rendered frame, in its entirety, to 'fp'. If
// notcurses_render() has not yet been called, nothing will be written. // notcurses_render() has not yet been called, nothing will be written.

@ -96,11 +96,11 @@ typedef struct ncplane {
// current presentation state of the terminal. it is carried across render // current presentation state of the terminal. it is carried across render
// instances. initialize everything to 0 on a terminal reset / startup. // instances. initialize everything to 0 on a terminal reset / startup.
typedef struct renderstate { typedef struct renderstate {
// we assemble the encoded output in a POSIX memstream, and keep it around // we assemble the encoded (rasterized) output in a POSIX memstream, and keep
// between uses. this could be a problem if it ever tremendously spiked, but // it around between uses. this could be a problem if it ever tremendously
// that's a highly unlikely situation. // spiked, but that's a highly unlikely situation.
char* mstream; // buffer for rendering memstream, see open_memstream(3) char* mstream; // buffer for rasterizing memstream, see open_memstream(3)
FILE* mstreamfp;// FILE* for rendering memstream FILE* mstreamfp;// FILE* for rasterizing memstream
size_t mstrsize;// size of rendering memstream size_t mstrsize;// size of rendering memstream
// the current cursor position. this is independent of whether the cursor is // 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 uint64_t flags; // copied in ncdirect_init() from param
} ncdirect; } 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 { typedef struct ncpile {
ncplane* top; // topmost plane, never NULL ncplane* top; // topmost plane, never NULL
ncplane* bottom; // bottommost plane, never NULL ncplane* bottom; // bottommost plane, never NULL
struct notcurses* nc; // notcurses context struct notcurses* nc; // notcurses context
struct ncpile *prev, *next; // circular list struct ncpile *prev, *next; // circular list
size_t crenderlen; // size of crender array in bytes
struct crender* crender; // crender array
} ncpile; } ncpile;
// the standard pile can be reached through ->stdplane. // the standard pile can be reached through ->stdplane.

@ -271,6 +271,7 @@ ncpile_destroy(ncpile* pile){
if(pile){ if(pile){
pile->prev->next = pile->next; pile->prev->next = pile->next;
pile->next->prev = pile->prev; pile->next->prev = pile->prev;
free(pile->crender);
free(pile); free(pile);
} }
} }
@ -315,6 +316,8 @@ make_ncpile(notcurses* nc, ncplane* n){
n->pile = ret; n->pile = ret;
n->above = NULL; n->above = NULL;
n->below = NULL; n->below = NULL;
ret->crenderlen = 0;
ret->crender = NULL;
} }
return ret; return ret;
} }

@ -152,11 +152,10 @@ int cell_duplicate(ncplane* n, cell* targ, const cell* c){
return 0; return 0;
} }
// Extracellular state for a cell during the render process. This array is // Extracellular state for a cell during the render process. There is one
// passed along to rasterization, which uses only the 'damaged' bools. There // crender per rendered cell, and they are initialized to all zeroes.
// is one crender per rendered cell, and they are initialized to all zeroes.
struct crender { struct crender {
const ncplane *p; const ncplane *p; // source of glyph for this cell
cell c; cell c;
unsigned fgblends; unsigned fgblends;
unsigned bgblends; 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 // it's not a pure memset(), because CELL_ALPHA_OPAQUE is the zero value, and
// we need CELL_ALPHA_TRANSPARENT // we need CELL_ALPHA_TRANSPARENT
static void static inline void
init_rvec(struct crender* rvec, int totalcells){ init_rvec(struct crender* rvec, int totalcells){
memset(rvec, 0, sizeof(*rvec) * totalcells); memset(rvec, 0, sizeof(*rvec) * totalcells);
for(int t = 0 ; t < totalcells ; ++t){ for(int t = 0 ; t < totalcells ; ++t){
@ -709,12 +708,12 @@ update_palette(notcurses* nc, FILE* out){
return 0; return 0;
} }
// sync the cursor to the specified location with as little overhead as // sync the drawing position to the specified location with as little overhead
// possible (with nothing, if already at the right location). // as possible (with nothing, if already at the right location).
// FIXME fall back to synthesized moves in the absence of capabilities (i.e. // FIXME fall back to synthesized moves in the absence of capabilities (i.e.
// textronix lacks cup; fake it with horiz+vert moves) // textronix lacks cup; fake it with horiz+vert moves)
static inline int 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; int ret = 0;
if(nc->rstate.y == y){ // only need move x if(nc->rstate.y == y){ // only need move x
const int xdiff = x - nc->rstate.x; const int xdiff = x - nc->rstate.x;
@ -779,7 +778,7 @@ notcurses_rasterize_inner(notcurses* nc, const struct crender* rvec, FILE* out){
} }
}else{ }else{
++nc->stats.cellemissions; ++nc->stats.cellemissions;
if(stage_cursor(nc, out, y, x)){ if(goto_location(nc, out, y, x)){
return -1; return -1;
} }
// 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
@ -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 // which cells were changed. We solve for each coordinate's cell by walking
// down the z-buffer, looking at intersections with ncplanes. This implies // down the z-buffer, looking at intersections with ncplanes. This implies
// locking down the EGC, the attributes, and the channels for each cell. // 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, ncpile_render_internal(ncplane* n, struct crender* rvec, int leny, int lenx,
int absy, int absx){ int absy, int absx){
ncplane* p = ncplane_pile(n)->top; 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); paint(p, rvec, leny, lenx, absy, absx);
p = p->below; p = p->below;
} }
return 0;
} }
int ncpile_render(ncplane* n){ int ncpile_rasterize(ncplane* n){
struct timespec start, rasterdone; struct timespec start, writedone;
clock_gettime(CLOCK_MONOTONIC, &start); clock_gettime(CLOCK_MONOTONIC, &start);
int dimy, dimx; int dimy, dimx;
const struct ncpile* pile = ncplane_pile(n);
notcurses_resize(ncplane_notcurses(n), &dimy, &dimx); notcurses_resize(ncplane_notcurses(n), &dimy, &dimx);
int bytes = -1; postpaint(ncplane_notcurses(n)->lastframe, dimy, dimx, pile->crender,
const size_t crenderlen = sizeof(struct crender) * dimy * dimx; &ncplane_notcurses(n)->pool);
struct crender* crender = malloc(crenderlen); int bytes = notcurses_rasterize(ncplane_notcurses(n), pile->crender,
init_rvec(crender, crenderlen / sizeof(struct crender)); ncplane_notcurses(n)->rstate.mstreamfp);
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);
}
// accepts -1 as an indication of failure // accepts -1 as an indication of failure
update_render_bytes(&ncplane_notcurses(n)->stats, bytes); update_render_bytes(&ncplane_notcurses(n)->stats, bytes);
free(crender);
if(bytes < 0){ if(bytes < 0){
return -1; return -1;
} }
struct timespec writedone;
clock_gettime(CLOCK_MONOTONIC, &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; return 0;
} }
int notcurses_render(notcurses* nc){ 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 // 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 // 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. // 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){ int notcurses_render_to_buffer(notcurses* nc, char** buf, size_t* buflen){
struct timespec start, rasterdone; struct ncplane* stdn = notcurses_stdplane(nc);
clock_gettime(CLOCK_MONOTONIC, &start); if(ncpile_render(stdn)){
int dimy, dimx; return -1;
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);
} }
int bytes = notcurses_rasterize_inner(nc, ncplane_pile(stdn)->crender, nc->rstate.mstreamfp);
update_render_bytes(&nc->stats, bytes); update_render_bytes(&nc->stats, bytes);
if(bytes < 0){ if(bytes < 0){
return -1; return -1;
@ -1188,7 +1202,7 @@ int notcurses_cursor_enable(notcurses* nc, int y, int x){
if(nc->ttyfd < 0 || !nc->tcache.cnorm){ if(nc->ttyfd < 0 || !nc->tcache.cnorm){
return -1; 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; return -1;
} }
// if we were already positive, we're already visible, no need to write cnorm // if we were already positive, we're already visible, no need to write cnorm

Loading…
Cancel
Save