1 #include "internal.h"
2 #include "visual-details.h"
3 #include <stdatomic.h>
4
5 static atomic_uint_fast32_t sprixelid_nonce;
6
sprixel_debug(const sprixel * s,FILE * out)7 void sprixel_debug(const sprixel* s, FILE* out){
8 fprintf(out, "Sprixel %d (%p) %" PRIu64 "B %dx%d (%dx%d) @%d/%d state: %d\n",
9 s->id, s, s->glyph.used, s->dimy, s->dimx, s->pixy, s->pixx,
10 s->n ? s->n->absy : 0, s->n ? s->n->absx : 0,
11 s->invalidated);
12 if(s->n){
13 int idx = 0;
14 for(unsigned y = 0 ; y < s->dimy ; ++y){
15 for(unsigned x = 0 ; x < s->dimx ; ++x){
16 fprintf(out, "%d", s->n->tam[idx].state);
17 ++idx;
18 }
19 fprintf(out, "\n");
20 }
21 idx = 0;
22 for(unsigned y = 0 ; y < s->dimy ; ++y){
23 for(unsigned x = 0 ; x < s->dimx ; ++x){
24 if(s->n->tam[idx].state == SPRIXCELL_ANNIHILATED){
25 if(s->n->tam[idx].auxvector){
26 fprintf(out, "%03d] %p\n", idx, s->n->tam[idx].auxvector);
27 }else{
28 fprintf(out, "%03d] missing!\n", idx);
29 }
30 }
31 ++idx;
32 }
33 }
34 }
35 }
36
37 // doesn't splice us out of any lists, just frees
sprixel_free(sprixel * s)38 void sprixel_free(sprixel* s){
39 if(s){
40 loginfo("Destroying sprixel %u\n", s->id);
41 if(s->n){
42 s->n->sprite = NULL;
43 }
44 sixelmap_free(s->smap);
45 free(s->needs_refresh);
46 fbuf_free(&s->glyph);
47 free(s);
48 }
49 }
50
sprixel_recycle(ncplane * n)51 sprixel* sprixel_recycle(ncplane* n){
52 assert(n->sprite);
53 const notcurses* nc = ncplane_notcurses_const(n);
54 if(nc->tcache.pixel_implementation >= NCPIXEL_KITTY_STATIC){
55 sprixel* hides = n->sprite;
56 int dimy = hides->dimy;
57 int dimx = hides->dimx;
58 sprixel_hide(hides);
59 return sprixel_alloc(n, dimy, dimx);
60 }
61 sixelmap_free(n->sprite->smap);
62 n->sprite->smap = NULL;
63 return n->sprite;
64 }
65
66 // store the original (absolute) coordinates from which we moved, so that
67 // we can invalidate them in sprite_draw().
sprixel_movefrom(sprixel * s,int y,int x)68 void sprixel_movefrom(sprixel* s, int y, int x){
69 if(s->invalidated != SPRIXEL_HIDE && s->invalidated != SPRIXEL_UNSEEN){
70 if(s->invalidated != SPRIXEL_MOVED){
71 // FIXME if we're Sixel, we need to effect any wipes that were run
72 // (we normally don't because redisplaying sixel doesn't change
73 // what's there--you can't "write transparency"). this is probably
74 // best done by conditionally reblitting the sixel(?).
75 //fprintf(stderr, "SETTING TO MOVE: %d/%d was: %d\n", y, x, s->invalidated);
76 s->invalidated = SPRIXEL_MOVED;
77 s->movedfromy = y;
78 s->movedfromx = x;
79 }
80 }
81 }
82
sprixel_hide(sprixel * s)83 void sprixel_hide(sprixel* s){
84 if(ncplane_pile(s->n) == NULL){ // ncdirect case; destroy now
85 sprixel_free(s);
86 return;
87 }
88 // otherwise, it'll be killed in the next rendering cycle.
89 if(s->invalidated != SPRIXEL_HIDE){
90 loginfo("Marking sprixel %u hidden\n", s->id);
91 s->invalidated = SPRIXEL_HIDE;
92 s->movedfromy = ncplane_abs_y(s->n);
93 s->movedfromx = ncplane_abs_x(s->n);
94 // guard; might have already been replaced
95 if(s->n){
96 s->n->sprite = NULL;
97 s->n = NULL;
98 }
99 }
100 }
101
102 // y and x are absolute coordinates.
sprixel_invalidate(sprixel * s,int y,int x)103 void sprixel_invalidate(sprixel* s, int y, int x){
104 //fprintf(stderr, "INVALIDATING AT %d/%d\n", y, x);
105 if(s->invalidated == SPRIXEL_QUIESCENT && s->n){
106 int localy = y - s->n->absy;
107 int localx = x - s->n->absx;
108 //fprintf(stderr, "INVALIDATING AT %d/%d (%d/%d) TAM: %d\n", y, x, localy, localx, s->n->tam[localy * s->dimx + localx].state);
109 if(s->n->tam[localy * s->dimx + localx].state != SPRIXCELL_TRANSPARENT &&
110 s->n->tam[localy * s->dimx + localx].state != SPRIXCELL_ANNIHILATED &&
111 s->n->tam[localy * s->dimx + localx].state != SPRIXCELL_ANNIHILATED_TRANS){
112 s->invalidated = SPRIXEL_INVALIDATED;
113 }
114 }
115 }
116
sprixel_alloc(ncplane * n,int dimy,int dimx)117 sprixel* sprixel_alloc(ncplane* n, int dimy, int dimx){
118 sprixel* ret = malloc(sizeof(sprixel));
119 if(ret == NULL){
120 return NULL;
121 }
122 memset(ret, 0, sizeof(*ret));
123 if(fbuf_init(&ret->glyph)){
124 free(ret);
125 return NULL;
126 }
127 ret->n = n;
128 ret->dimy = dimy;
129 ret->dimx = dimx;
130 ret->id = ++sprixelid_nonce;
131 ret->needs_refresh = NULL;
132 if(ret->id >= 0x1000000){
133 ret->id = 1;
134 sprixelid_nonce = 1;
135 }
136 //fprintf(stderr, "LOOKING AT %p (p->n = %p)\n", ret, ret->n);
137 if(ncplane_pile(ret->n)){ // rendered mode
138 ncpile* np = ncplane_pile(ret->n);
139 if( (ret->next = np->sprixelcache) ){
140 ret->next->prev = ret;
141 }
142 np->sprixelcache = ret;
143 ret->prev = NULL;
144 //fprintf(stderr, "%p %p %p\n", nc->sprixelcache, ret, nc->sprixelcache->next);
145 }else{ // ncdirect case
146 ret->next = ret->prev = NULL;
147 }
148 return ret;
149 }
150
151 // |pixy| and |pixx| are the output pixel geometry (i.e. |pixy| must be a
152 // multiple of 6 for sixel). output coverage ought already have been loaded.
153 // takes ownership of 's' on success. frees any existing glyph.
sprixel_load(sprixel * spx,fbuf * f,unsigned pixy,unsigned pixx,int parse_start,sprixel_e state)154 int sprixel_load(sprixel* spx, fbuf* f, unsigned pixy, unsigned pixx,
155 int parse_start, sprixel_e state){
156 assert(spx->n);
157 if(&spx->glyph != f){
158 fbuf_free(&spx->glyph);
159 memcpy(&spx->glyph, f, sizeof(*f));
160 }
161 spx->invalidated = state;
162 spx->pixx = pixx;
163 spx->pixy = pixy;
164 spx->parse_start = parse_start;
165 return 0;
166 }
167
168 // returns 1 if already annihilated, 0 if we successfully annihilated the cell,
169 // or -1 if we could not annihilate the cell (i.e. we're sixel).
sprite_wipe(const notcurses * nc,sprixel * s,int ycell,int xcell)170 int sprite_wipe(const notcurses* nc, sprixel* s, int ycell, int xcell){
171 assert(s->n);
172 int idx = s->dimx * ycell + xcell;
173 if(s->n->tam[idx].state == SPRIXCELL_TRANSPARENT){
174 // need to make a transparent auxvec, because a reload will force us to
175 // update said auxvec, but needn't actually change the glyph. auxvec will
176 // be entirely 0s coming from pixel_trans_auxvec().
177 if(s->n->tam[idx].auxvector == NULL){
178 if(nc->tcache.pixel_trans_auxvec){
179 s->n->tam[idx].auxvector = nc->tcache.pixel_trans_auxvec(ncplane_pile(s->n));
180 if(s->n->tam[idx].auxvector == NULL){
181 return -1;
182 }
183 }
184 }
185 // no need to update to INVALIDATED; no redraw is necessary
186 s->n->tam[idx].state = SPRIXCELL_ANNIHILATED_TRANS;
187 return 1;
188 }
189 if(s->n->tam[idx].state == SPRIXCELL_ANNIHILATED_TRANS ||
190 s->n->tam[idx].state == SPRIXCELL_ANNIHILATED){
191 //fprintf(stderr, "CACHED WIPE %d %d/%d\n", s->id, ycell, xcell);
192 return 0;
193 }
194 logdebug("wiping %p %d %d/%d\n", s->n->tam, idx, ycell, xcell);
195 int r = nc->tcache.pixel_wipe(s, ycell, xcell);
196 //fprintf(stderr, "WIPED %d %d/%d ret=%d\n", s->id, ycell, xcell, r);
197 // mark the cell as annihilated whether we actually scrubbed it or not,
198 // so that we use this fact should we move to another frame
199 s->n->tam[idx].state = SPRIXCELL_ANNIHILATED;
200 assert(s->n->tam[idx].auxvector);
201 return r;
202 }
203
sprite_clear_all(const tinfo * t,fbuf * f)204 int sprite_clear_all(const tinfo* t, fbuf* f){
205 if(t->pixel_clear_all == NULL){
206 return 0;
207 }
208 return t->pixel_clear_all(f);
209 }
210
211 // we don't want to seed the process-wide prng, but we do want to stir in a
212 // bit of randomness for our purposes. we probably ought just use platform-
213 // specific APIs, but for now, throw the timestamp in there, lame FIXME.
sprite_init(const tinfo * t,int fd)214 int sprite_init(const tinfo* t, int fd){
215 struct timeval tv;
216 gettimeofday(&tv, NULL);
217 int stir = (tv.tv_sec >> 3) ^ tv.tv_usec;
218 sprixelid_nonce = (rand() ^ stir) % 0xffffffu;
219 if(t->pixel_init == NULL){
220 return 0;
221 }
222 return t->pixel_init(fd);
223 }
224
sprixel_rescale(sprixel * spx,unsigned ncellpxy,unsigned ncellpxx)225 int sprixel_rescale(sprixel* spx, unsigned ncellpxy, unsigned ncellpxx){
226 assert(spx->n);
227 loginfo("rescaling -> %ux%u\n", ncellpxy, ncellpxx);
228 // FIXME need adjust for sixel (scale_height)
229 int nrows = (spx->pixy + (ncellpxy - 1)) / ncellpxy;
230 int ncols = (spx->pixx + (ncellpxx - 1)) / ncellpxx;
231 tament* ntam = create_tam(nrows, ncols);
232 if(ntam == NULL){
233 return -1;
234 }
235 for(unsigned y = 0 ; y < spx->dimy ; ++y){
236 for(unsigned x = 0 ; x < spx->dimx ; ++x){
237 sprite_rebuild(ncplane_notcurses(spx->n), spx, y, x);
238 }
239 }
240 ncplane* ncopy = spx->n;
241 destroy_tam(spx->n);
242 // spx->n->tam has been reset, so it will not be resized herein
243 ncplane_resize_simple(spx->n, nrows, ncols);
244 spx->n = ncopy;
245 spx->n->sprite = spx;
246 spx->n->tam = ntam;
247 spx->dimy = nrows;
248 spx->dimx = ncols;
249 return 0;
250 }
251