From 8a4f3b78adc1a22991a49153d4d7a3aee0e3507a Mon Sep 17 00:00:00 2001 From: nick black Date: Sun, 31 Jan 2021 15:35:37 -0500 Subject: [PATCH] Write blit quadrants to channel Reclaim the "blitted" and "full foreground" bits in the channels. Instead, we now write four bits, encoding the four quadrants we might occupy as the result of a blit. These four imply the previous two, leaving us with four free bits remaining in the channels. This opens a clear path to O(1)-time, zero-space blitter stacking #1068. w00t! --- USAGE.md | 25 ++++++++++++---- include/notcurses/notcurses.h | 14 ++++----- src/lib/blit.c | 56 +++++++++++++++++++++++------------ src/lib/internal.h | 21 ++++++++----- tests/channel.cpp | 3 ++ 5 files changed, 80 insertions(+), 39 deletions(-) diff --git a/USAGE.md b/USAGE.md index 4bb4ef9484..68eb304755 100644 --- a/USAGE.md +++ b/USAGE.md @@ -1601,11 +1601,22 @@ manner. // characters, followed by a NUL terminator. // // Multi-column characters can only have a single style/color throughout. +// Existence is suffering, and thus wcwidth() is not reliable. It's just +// quoting whether or not the EGC contains a "Wide Asian" double-width +// character. This is set for some things, like most emoji, and not set for +// other things, like cuneiform. True display width is a *function of the +// font and terminal*. Among the longest Unicode codepoints is +// +// U+FDFD ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM ﷽ +// +// wcwidth() rather optimistically claims this most exalted glyph to occupy +// a single column. BiDi text is too complicated for me to even get into here. +// Be assured there are no easy answers; ours is indeed a disturbing Universe. // // Each nccell occupies 16 static bytes (128 bits). The surface is thus ~1.6MB // for a (pretty large) 500x200 terminal. At 80x43, it's less than 64KB. -// Dynamic requirements can add up to 16MB to an ncplane, but such large pools -// are unlikely in common use. +// Dynamic requirements (the egcpool) can add up to 16MB to an ncplane, but +// such large pools are unlikely in common use. // // We implement some small alpha compositing. Foreground and background both // have two bits of inverted alpha. The actual grapheme written to a cell is @@ -1624,6 +1635,9 @@ manner. // meaningfully set transparency, but it can be mixed into a cascading color. // RGB is used if neither default terminal colors nor palette indexing are in // play, and fully supports all transparency options. +// +// This structure is exposed only so that most functions can be inlined. Do not +// directly modify or access the fields of this structure; use the API. typedef struct nccell { // These 32 bits, together with the associated plane's associated egcpool, // completely define this cell's EGC. Unless the EGC requires more than four @@ -1665,12 +1679,13 @@ typedef struct nccell { // can be determined by checking whether ->gcluster is zero. uint8_t width; // 1B → 6B (8 bits of EGC column width) uint16_t stylemask; // 2B → 8B (16 bits of NCSTYLE_* attributes) - // (channels & 0x8000000000000000ull): reserved, must be 0 + // (channels & 0x8000000000000000ull): blitted to upper-left quadrant // (channels & 0x4000000000000000ull): foreground is *not* "default color" // (channels & 0x3000000000000000ull): foreground alpha (2 bits) // (channels & 0x0800000000000000ull): foreground uses palette index - // (channels & 0x0400000000000000ull): glyph is entirely foreground - // (channels & 0x0300000000000000ull): reserved, must be 0 + // (channels & 0x0400000000000000ull): blitted to upper-right quadrant + // (channels & 0x0200000000000000ull): blitted to lower-left quadrant + // (channels & 0x0100000000000000ull): blitted to lower-right quadrant // (channels & 0x00ffffff00000000ull): foreground in 3x8 RGB (rrggbb) // (channels & 0x0000000080000000ull): reserved, must be 0 // (channels & 0x0000000040000000ull): background is *not* "default color" diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index f86cba8986..b170eebdcd 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -591,20 +591,20 @@ typedef struct nccell { // can be determined by checking whether ->gcluster is zero. uint8_t width; // 1B → 6B (8 bits of EGC column width) uint16_t stylemask; // 2B → 8B (16 bits of NCSTYLE_* attributes) - // (channels & 0x8000000000000000ull): reserved, must be 0 + // (channels & 0x8000000000000000ull): blitted to upper-left quadrant // (channels & 0x4000000000000000ull): foreground is *not* "default color" // (channels & 0x3000000000000000ull): foreground alpha (2 bits) // (channels & 0x0800000000000000ull): foreground uses palette index - // (channels & 0x0400000000000000ull): entirely foreground (used for optimization in rasterization) - // (channels & 0x0300000000000000ull): reserved, must be 0 - // (channels & 0x00ffffff00000000ull): foreground in 3x8 RGB (rrggbb) + // (channels & 0x0400000000000000ull): blitted to upper-right quadrant + // (channels & 0x0200000000000000ull): blitted to lower-left quadrant + // (channels & 0x0100000000000000ull): blitted to lower-right quadrant + // (channels & 0x00ffffff00000000ull): foreground in 3x8 RGB (rrggbb) / pindex // (channels & 0x0000000080000000ull): reserved, must be 0 // (channels & 0x0000000040000000ull): background is *not* "default color" // (channels & 0x0000000030000000ull): background alpha (2 bits) // (channels & 0x0000000008000000ull): background uses palette index - // (channels & 0x0000000004000000ull): drawn by ncvisual (used to trigger blitter stacking) - // (channels & 0x0000000003000000ull): reserved, must be 0 - // (channels & 0x0000000000ffffffull): background in 3x8 RGB (rrggbb) + // (channels & 0x0000000007000000ull): reserved, must be 0 + // (channels & 0x0000000000ffffffull): background in 3x8 RGB (rrggbb) / pindex // At render time, these 24-bit values are quantized down to terminal // capabilities, if necessary. There's a clear path to 10-bit support should // we one day need it, but keep things cagey for now. "default color" is diff --git a/src/lib/blit.c b/src/lib/blit.c index f91ce3da50..770d757dd2 100644 --- a/src/lib/blit.c +++ b/src/lib/blit.c @@ -125,7 +125,6 @@ tria_blit(ncplane* nc, int placey, int placex, int linesize, } if(ffmpeg_trans_p(rgbbase_up[3]) || ffmpeg_trans_p(rgbbase_down[3])){ cell_set_bg_alpha(c, CELL_ALPHA_TRANSPARENT); - cell_set_blitted(c); if(ffmpeg_trans_p(rgbbase_up[3]) && ffmpeg_trans_p(rgbbase_down[3])){ cell_set_fg_alpha(c, CELL_ALPHA_TRANSPARENT); }else if(ffmpeg_trans_p(rgbbase_up[3])){ // down has the color @@ -133,6 +132,7 @@ tria_blit(ncplane* nc, int placey, int placex, int linesize, return -1; } cell_set_fg_rgb8(c, rgbbase_down[0], rgbbase_down[1], rgbbase_down[2]); + cell_set_blitquadrants(c, 0, 0, 1, 1); ++total; }else{ // up has the color // upper half block @@ -140,6 +140,7 @@ tria_blit(ncplane* nc, int placey, int placex, int linesize, return -1; } cell_set_fg_rgb8(c, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2]); + cell_set_blitquadrants(c, 1, 1, 0, 0); ++total; } }else{ @@ -309,14 +310,17 @@ qtrans_check(nccell* c, bool blendcolors, egc = ""; }else{ cell_set_fg_rgb8(c, rgbbase_br[0], rgbbase_br[1], rgbbase_br[2]); + cell_set_blitquadrants(c, 0, 0, 0, 1); egc = "▗"; } }else{ if(ffmpeg_trans_p(rgbbase_br[3])){ cell_set_fg_rgb8(c, rgbbase_bl[0], rgbbase_bl[1], rgbbase_bl[2]); + cell_set_blitquadrants(c, 0, 0, 1, 0); egc = "▖"; }else{ cell_set_fchannel(c, lerp(bl, br)); + cell_set_blitquadrants(c, 0, 0, 1, 1); egc = "▄"; } } @@ -324,16 +328,20 @@ qtrans_check(nccell* c, bool blendcolors, if(ffmpeg_trans_p(rgbbase_bl[3])){ if(ffmpeg_trans_p(rgbbase_br[3])){ // entire bottom is transparent cell_set_fg_rgb8(c, rgbbase_tr[0], rgbbase_tr[1], rgbbase_tr[2]); + cell_set_blitquadrants(c, 0, 1, 0, 0); egc = "▝"; }else{ cell_set_fchannel(c, lerp(tr, br)); + cell_set_blitquadrants(c, 0, 1, 0, 1); egc = "▐"; } }else if(ffmpeg_trans_p(rgbbase_br[3])){ // only br is transparent cell_set_fchannel(c, lerp(tr, bl)); + cell_set_blitquadrants(c, 0, 1, 1, 0); egc = "▞"; }else{ cell_set_fchannel(c, trilerp(tr, bl, br)); + cell_set_blitquadrants(c, 0, 1, 1, 1); egc = "▟"; } } @@ -342,28 +350,35 @@ qtrans_check(nccell* c, bool blendcolors, if(ffmpeg_trans_p(rgbbase_bl[3])){ if(ffmpeg_trans_p(rgbbase_br[3])){ cell_set_fg_rgb8(c, rgbbase_tl[0], rgbbase_tl[1], rgbbase_tl[2]); + cell_set_blitquadrants(c, 1, 0, 0, 0); egc = "▘"; }else{ cell_set_fchannel(c, lerp(tl, br)); + cell_set_blitquadrants(c, 1, 0, 0, 1); egc = "▚"; } }else if(ffmpeg_trans_p(rgbbase_br[3])){ cell_set_fchannel(c, lerp(tl, bl)); + cell_set_blitquadrants(c, 1, 0, 1, 0); egc = "▌"; }else{ cell_set_fchannel(c, trilerp(tl, bl, br)); + cell_set_blitquadrants(c, 1, 0, 1, 1); egc = "▙"; } }else if(ffmpeg_trans_p(rgbbase_bl[3])){ if(ffmpeg_trans_p(rgbbase_br[3])){ // entire bottom is transparent cell_set_fchannel(c, lerp(tl, tr)); + cell_set_blitquadrants(c, 1, 1, 0, 0); egc = "▀"; }else{ // only bl is transparent cell_set_fchannel(c, trilerp(tl, tr, br)); + cell_set_blitquadrants(c, 1, 1, 0, 1); egc = "▜"; } }else if(ffmpeg_trans_p(rgbbase_br[3])){ // only br is transparent cell_set_fchannel(c, trilerp(tl, tr, bl)); + cell_set_blitquadrants(c, 1, 1, 1, 0); egc = "▛"; }else{ return NULL; // no transparency @@ -434,8 +449,6 @@ quadrant_blit(ncplane* nc, int placey, int placex, int linesize, cell_set_bg_alpha(c, CELL_ALPHA_BLEND); cell_set_fg_alpha(c, CELL_ALPHA_BLEND); } - }else{ - cell_set_blitted(c); } if(*egc){ if(pool_blit_direct(&nc->pool, c, egc, strlen(egc), 1) <= 0){ @@ -548,15 +561,20 @@ sex_solver(const uint32_t rgbas[6], uint64_t* channels, bool blendcolors){ } static const char* -sex_trans_check(const uint32_t rgbas[6], uint64_t* channels, bool blendcolors){ +sex_trans_check(cell* c, const uint32_t rgbas[6], bool blendcolors){ + // bit is *set* where sextant *is not* + // 32: bottom right 16: bottom left + // 8: middle right 4: middle left + // 2: upper right 1: upper left static const char* sex[64] = { - "█", "🬻", "🬺", "🬹", "🬸", "🬷", "🬶", "🬵", "🬴", "🬳", "🬲", // 10 - "🬱", "🬰", "🬯", "🬮", "🬭", "🬬", "🬫", "🬪", "🬩", "🬨", "▐", // 21 - "🬧", "🬦", "🬥", "🬤", "🬣", "🬢", "🬡", "🬠", "🬟", // 30 - "🬞", "🬝", "🬜", "🬛", "🬚", "🬙", "🬘", "🬗", "🬖", "🬕", // 40 - "🬔", "▌", "🬓", "🬒", "🬑", "🬐", "🬏", "🬎", "🬍", "🬌", // 50 - "🬋", "🬊", "🬉", "🬈", "🬇", "🬆", "🬅", "🬄", "🬃", "🬂", // 60 - "🬁", "🬀", " ", + "█", "🬻", "🬺", "🬹", "🬸", "🬷", "🬶", "🬵", + "🬴", "🬳", "🬲", "🬱", "🬰", "🬯", "🬮", "🬭", + "🬬", "🬫", "🬪", "🬩", "🬨", "▐", "🬧", "🬦", + "🬥", "🬤", "🬣", "🬢", "🬡", "🬠", "🬟", "🬞", + "🬝", "🬜", "🬛", "🬚", "🬙", "🬘", "🬗", "🬖", + "🬕", "🬔", "▌", "🬓", "🬒", "🬑", "🬐", "🬏", + "🬎", "🬍", "🬌", "🬋", "🬊", "🬉", "🬈", "🬇", + "🬆", "🬅", "🬄", "🬃", "🬂", "🬁", "🬀", " ", }; unsigned transstring = 0; unsigned r = 0, g = 0, b = 0; @@ -574,21 +592,23 @@ sex_trans_check(const uint32_t rgbas[6], uint64_t* channels, bool blendcolors){ if(transstring == 0){ return NULL; } - channels_set_bg_alpha(channels, CELL_ALPHA_TRANSPARENT); + cell_set_bg_alpha(c, CELL_ALPHA_TRANSPARENT); // there were some transparent pixels. since they get priority, the foreground // is just a general lerp across non-transparent pixels. const char* egc = sex[transstring]; - channels_set_bg_alpha(channels, CELL_ALPHA_TRANSPARENT); + cell_set_bg_alpha(c, CELL_ALPHA_TRANSPARENT); //fprintf(stderr, "transtring: %u egc: %s\n", transtring, egc); if(*egc == ' '){ // entirely transparent - channels_set_fg_alpha(channels, CELL_ALPHA_TRANSPARENT); + cell_set_fg_alpha(c, CELL_ALPHA_TRANSPARENT); return ""; }else{ // partially transparent, thus div >= 1 //fprintf(stderr, "div: %u r: %u g: %u b: %u\n", div, r, g, b); - channels_set_fchannel(channels, generalerp(r, g, b, div)); + cell_set_fchannel(c, generalerp(r, g, b, div)); if(blendcolors){ - channels_set_fg_alpha(channels, CELL_ALPHA_BLEND); + cell_set_fg_alpha(c, CELL_ALPHA_BLEND); } + cell_set_blitquadrants(c, !(transstring & 5u), !(transstring & 10u), + !(transstring & 20u), !(transstring & 40u)); } return egc; } @@ -632,11 +652,9 @@ sextant_blit(ncplane* nc, int placey, int placex, int linesize, nccell* c = ncplane_cell_ref_yx(nc, y, x); c->channels = 0; c->stylemask = 0; - const char* egc = sex_trans_check(rgbas, &c->channels, blendcolors); + const char* egc = sex_trans_check(c, rgbas, blendcolors); if(egc == NULL){ egc = sex_solver(rgbas, &c->channels, blendcolors); - }else{ - cell_set_blitted(c); } //fprintf(stderr, "sex EGC: %s channels: %016lx\n", egc, c->channels); if(*egc){ diff --git a/src/lib/internal.h b/src/lib/internal.h index 4ff8b3921e..b859fb4ec8 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -33,11 +33,11 @@ struct ncvisual_details; // Does this glyph completely obscure the background? If so, there's no need // to emit a background when rasterizing, a small optimization. -#define CELL_NOBACKGROUND_MASK 0x0400000000000000ull +#define CELL_NOBACKGROUND_MASK 0x8700000000000000ull // Was this glyph drawn as part of an ncvisual? If so, we need to honor // blitter stacking rather than the standard trichannel solver. -#define CELL_BLITTERSTACK_MASK 0x0000000004000000ull +#define CELL_BLITTERSTACK_MASK 0x8700000000000000ull // we can't define multipart ncvisual here, because OIIO requires C++ syntax, // and we can't go throwing C++ syntax into this header. so it goes. @@ -849,22 +849,27 @@ box_corner_needs(unsigned ctlword){ // solid or shaded block, or certain emoji). static inline bool cell_nobackground_p(const nccell* c){ - return c->channels & CELL_NOBACKGROUND_MASK; + return (c->channels & CELL_NOBACKGROUND_MASK) == CELL_NOBACKGROUND_MASK; } -// True if the cell was blitted as part of an ncvisual, and has a transparent -// background. +// True iff the cell was blitted as part of an ncvisual, and has a transparent +// background (being the only case where CELL_BLITTERSTACK_MASK bits are set). static inline bool cell_blitted_p(const nccell* c){ - return c->channels & CELL_BLITTERSTACK_MASK; + return c->channels & CELL_BLITTERSTACK_MASK; // any of the four bits is fine } // Set this whenever blitting an ncvisual, when we have a transparent // background. In such cases, ncvisuals underneath the cell must be rendered // slightly differently. static inline void -cell_set_blitted(nccell* c){ - c->channels |= CELL_BLITTERSTACK_MASK; +cell_set_blitquadrants(nccell* c, unsigned tl, unsigned tr, unsigned bl, unsigned br){ + // FIXME want a static assert that these four constants OR together to + // equal CELL_BLITTERSTACK_MASK, bah + c->channels |= tl ? 0x8000000000000000ull : 0; + c->channels |= tr ? 0x0400000000000000ull : 0; + c->channels |= bl ? 0x0200000000000000ull : 0; + c->channels |= br ? 0x0100000000000000ull : 0; } // Destroy a plane and all its bound descendants. diff --git a/tests/channel.cpp b/tests/channel.cpp index 074f9d1374..bc1a5dbb61 100644 --- a/tests/channel.cpp +++ b/tests/channel.cpp @@ -1,5 +1,8 @@ #include "main.h" +// These tests primarily check against expected constant values, which is +// useful in terms of maintaining ABI, but annoying when they do change. + TEST_CASE("ChannelGetRGB") { const struct t { uint32_t channel;