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