Skip to content

Commit

Permalink
Write blit quadrants to channel
Browse files Browse the repository at this point in the history
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!
  • Loading branch information
dankamongmen committed Jan 31, 2021
1 parent 1861be0 commit 8a4f3b7
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 39 deletions.
25 changes: 20 additions & 5 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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"
Expand Down
14 changes: 7 additions & 7 deletions include/notcurses/notcurses.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
56 changes: 37 additions & 19 deletions src/lib/blit.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,21 +125,22 @@ 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
if(pool_blit_direct(&nc->pool, c, "\u2584", strlen("\u2584"), 1) <= 0){
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
if(pool_blit_direct(&nc->pool, c, "\u2580", strlen("\u2580"), 1) <= 0){
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{
Expand Down Expand Up @@ -309,31 +310,38 @@ 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 = "▄";
}
}
}else{ // top right is foreground, top left is transparent
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 = "▟";
}
}
Expand All @@ -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
Expand Down Expand Up @@ -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){
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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){
Expand Down
21 changes: 13 additions & 8 deletions src/lib/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions tests/channel.cpp
Original file line number Diff line number Diff line change
@@ -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;
Expand Down

0 comments on commit 8a4f3b7

Please sign in to comment.