1 #include "demo.h"
2 #include <pthread.h>
3 #include <stdatomic.h>
4 
5 // this can take a long time, especially in large terminals; we cap execution
6 // at fifteen seconds (the true intended runtime), and drop frames when behind.
7 #define MAX_SECONDS 15
8 
9 // issue #2390 adds ncvisual_frame_count(). until then...FIXME
10 #define VIDEO_FRAMES 862
11 
12 static const char* leg[] = {
13 "                              88              88            88           88                          88             88               88                        ",
14 "                              \"\"              88            88           88                          88             \"\"               \"\"                 ,d     ",
15 "                                              88            88           88                          88                                                 88     ",
16 "  ,adPPYYba,     8b,dPPYba,   88   ,adPPYba,  88   ,d8      88,dPPYba,   88  ,adPPYYba,   ,adPPYba,  88   ,d8       88   ,adPPYba,   88  8b,dPPYba,  MM88MMM   ",
17 "  \"\"     `Y8     88P'   `\"8a  88  a8\"     \"\"  88 ,a8\"       88P'    \"8a  88  \"\"     `Y8  a8\"     \"\"  88 ,a8\"        88  a8\"     \"8a  88  88P'   `\"8a   88      ",
18 "  ,adPPPPP88     88       88  88  8b          8888[         88       d8  88  ,adPPPPP88  8b          8888[          88  8b       d8  88  88       88   88      ",
19 "  88,    ,88     88       88  88  \"8a,   ,aa  88`\"Yba,      88b,   ,a8\"  88  88,    ,88  \"8a,   ,aa  88`\"Yba,       88  \"8a,   ,a8\"  88  88       88   88,     ",
20 "  `\"8bbdP\"Y8     88       88  88   `\"Ybbd8\"'  88   `Y8a     8Y\"Ybbd8\"'   88  `\"8bbdP\"Y8   `\"Ybbd8\"'  88   `Y8a      88   `\"YbbdP\"'   88  88       88   \"Y888   ",
21 "                                                                                                                   ,88                                         ",
22 "                                                                                                                 888P                                          ",
23 };
24 
25 static struct ncplane*
make_slider(struct notcurses * nc,int dimx)26 make_slider(struct notcurses* nc, int dimx){
27   const int len = strlen(leg[0]);
28   // 862 frames in the video
29   const int REPS = 862 / len + dimx / len + 2;
30   struct ncplane_options nopts = {
31     .y = 1,
32     .x = 0,
33     .rows = sizeof(leg) / sizeof(*leg),
34     .cols = len * REPS,
35     .name = "scrl",
36   };
37   struct ncplane* n = ncplane_create(notcurses_stdplane(nc), &nopts);
38   uint64_t channels = 0;
39   ncchannels_set_fg_alpha(&channels, NCALPHA_TRANSPARENT);
40   ncchannels_set_bg_alpha(&channels, NCALPHA_TRANSPARENT);
41   ncplane_set_base(n, " ", 0, channels);
42   ncplane_set_scrolling(n, true);
43   int r = 0x5f;
44   int g = 0xaf;
45   int b = 0x84;
46   ncplane_set_bg_alpha(n, NCALPHA_TRANSPARENT);
47   for(int x = 0 ; x < REPS ; ++x){
48     for(size_t l = 0 ; l < sizeof(leg) / sizeof(*leg) ; ++l){
49       ncplane_set_fg_rgb8_clipped(n, r + 0x8 * l, g + 0x8 * l, b + 0x8 * l);
50       if(ncplane_set_bg_rgb8(n, (l + 1) * 0x2, 0x20, (l + 1) * 0x2)){
51         ncplane_destroy(n);
52         return NULL;
53       }
54       if(ncplane_putstr_yx(n, l, x * len, leg[l]) != len){
55         ncplane_destroy(n);
56         return NULL;
57       }
58     }
59     int t = r;
60     r = g;
61     g = b;
62     b = t;
63   }
64   return n;
65 }
66 
67 static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
68 static pthread_mutex_t render_lock = PTHREAD_MUTEX_INITIALIZER;
69 
70 struct marsh {
71   struct notcurses* nc;
72   struct ncvisual* ncv;     // video stream, one copy per thread
73   struct ncplane* slider;   // text plane at top, sliding to the left
74   float dm;                 // delay multiplier
75   int next_frame;
76   uint64_t startns;         // when we started (CLOCK_MONOTONIC) for frame dropping
77   int* frame_to_render;     // shared; protected by renderlock
78   int* dropped;             // shared; protected by renderlock
79   struct ncplane** lplane;  // shared; plane to destroy, renderlocked
80 };
81 
82 // make a plane on a new pile suitable for rendering a frame of the video
83 static int
make_plane(struct notcurses * nc,struct ncplane ** t)84 make_plane(struct notcurses* nc, struct ncplane** t){
85   unsigned dimy, dimx;
86   notcurses_stddim_yx(nc, &dimy, &dimx);
87   // FIXME want a resizecb
88   struct ncplane_options opts = {
89     .rows = dimy - 1,
90     .cols = dimx,
91     .name = "bmap",
92   };
93   *t = ncpile_create(nc, &opts);
94   if(!*t){
95     return -1;
96   }
97   return 0;
98 }
99 
100 // returns the index of the next frame, which can immediately begin to be
101 // rendered onto the thread's plane. if we're behind the clock, we don't
102 // bother blitting it, and drop the plane to signal as much.
103 static int
get_next_frame(struct marsh * m,struct ncvisual_options * vopts)104 get_next_frame(struct marsh* m, struct ncvisual_options* vopts){
105   // one does the odds, and one the evens. load two unless we're the even,
106   // and it's the first frame.
107   if(m->next_frame){
108     if(ncvisual_decode(m->ncv)){
109       return -1;
110     }
111   }
112   if(ncvisual_decode(m->ncv)){
113     return -1;
114   }
115   uint64_t ns = clock_getns(CLOCK_MONOTONIC);
116   int ret = m->next_frame;
117   uint64_t deadline = m->startns + (m->next_frame + 1) * MAX_SECONDS * (NANOSECS_IN_SEC / VIDEO_FRAMES);
118   // if we've missed the deadline, drop the frame 99% of the time (you've still
119   // got to draw now and again, or else there's just the initial frame hanging
120   // there for ~900 frames of crap).
121   if(ns > deadline && (rand() % 100)){
122     ncplane_destroy(vopts->n);
123     vopts->n = NULL;
124   }else if(ncvisual_blit(m->nc, m->ncv, vopts) == NULL){
125     return -1;
126   }
127   m->next_frame += 2;
128   return ret;
129 }
130 
131 static void*
xray_thread(void * vmarsh)132 xray_thread(void *vmarsh){
133   struct marsh* m = vmarsh;
134   struct ncplane* stdn = notcurses_stdplane(m->nc);
135   int frame = -1;
136   struct ncvisual_options vopts = {
137     .x = NCALIGN_CENTER,
138     .y = NCALIGN_CENTER,
139     .scaling = NCSCALE_SCALE_HIRES,
140     .blitter = NCBLIT_PIXEL,
141     .flags = NCVISUAL_OPTION_VERALIGNED | NCVISUAL_OPTION_HORALIGNED
142               | NCVISUAL_OPTION_ADDALPHA,
143   };
144   int ret;
145   do{
146     if(make_plane(m->nc, &vopts.n)){
147       return NULL;
148     }
149     // if we're behind where we want to be time-wise, this will return the
150     // expected frame id, but vopts.n will be NULL (and the plane we created
151     // will have been destroyed). that frame has been dropped.
152     if((frame = get_next_frame(m, &vopts)) < 0){
153       ncplane_destroy(vopts.n);
154       // FIXME need to cancel other one; it won't be able to progress
155       return NULL;
156     }
157     ret = -1;
158     // only one thread can render the standard pile at a time
159     pthread_mutex_lock(&render_lock);
160     while(*m->frame_to_render != frame){
161       pthread_cond_wait(&cond, &render_lock);
162     }
163     int x = ncplane_x(m->slider);
164     if(ncplane_move_yx(m->slider, 1, x - 1) == 0){
165       // FIXME otherwise, increment a visible drop count?
166       if(vopts.n){
167         ncplane_reparent(vopts.n, notcurses_stdplane(m->nc));
168         ncplane_move_top(vopts.n);
169         ncplane_destroy(*m->lplane);
170         *m->lplane = vopts.n;
171         ncplane_set_fg_rgb8_clipped(stdn, 96 + *m->dropped / 2, 0, 0x80);
172         ncplane_printf_aligned(stdn, 1 + ncplane_dim_y(m->slider),
173                                NCALIGN_RIGHT, "%d dropped frame%s",
174                                *m->dropped, *m->dropped == 0 ? "s ��" :
175                                *m->dropped == 1 ? " �� " :
176                                *m->dropped < 10 ? "s ��" :
177                                *m->dropped < 100 ? "s ��" :
178                                *m->dropped < 250 ? "s ��" :
179                                *m->dropped < 450 ? "s ��" :
180                                *m->dropped < 700 ? "s ��" : "s ��");
181         ret = demo_render(m->nc);
182       }else{
183         // FIXME i'd like to at least render the updated drop count and the
184         // moved slider, but even that's too slow with stupid shitty sixel,
185         // due to redrawing far too much of it see #2380
186         ++*m->dropped;
187         ret = 0;
188       }
189     }
190     *m->frame_to_render = frame + 1;
191     pthread_mutex_unlock(&render_lock);
192     pthread_cond_signal(&cond);
193     vopts.n = NULL;
194   }while(ret == 0);
195   return NULL;
196 }
197 
xray_demo(struct notcurses * nc,uint64_t startns)198 int xray_demo(struct notcurses* nc, uint64_t startns){
199   if(!notcurses_canopen_videos(nc)){
200     return 0;
201   }
202   unsigned dimx, dimy;
203   notcurses_term_dim_yx(nc, &dimy, &dimx);
204   ncplane_erase(notcurses_stdplane(nc));
205   char* path = find_data("notcursesIII.mov");
206   struct ncvisual* ncv1 = ncvisual_from_file(path);
207   struct ncvisual* ncv2 = ncvisual_from_file(path);
208   free(path);
209   if(ncv1 == NULL || ncv2 == NULL){
210     return -1;
211   }
212   struct ncplane* slider = make_slider(nc, dimx);
213   if(slider == NULL){
214     ncvisual_destroy(ncv1);
215     ncvisual_destroy(ncv2);
216     return -1;
217   }
218   uint64_t stdc = 0;
219   ncchannels_set_bg_rgb(&stdc, 0);
220   ncplane_set_base(notcurses_stdplane(nc), "", 0, stdc);
221   ncplane_set_bg_rgb(notcurses_stdplane(nc), 0);
222   // returns non-zero if the selected blitter isn't available
223   pthread_t tid1, tid2;
224   int last_frame = 0;
225   int dropped = 0;
226   struct ncplane* kplane = NULL; // to kill
227   struct marsh m1 = {
228     .slider = slider,
229     .nc = nc,
230     .next_frame = 0,
231     .frame_to_render = &last_frame,
232     .dm = notcurses_check_pixel_support(nc) ? 0 : 0.5 * delaymultiplier,
233     .ncv = ncv1,
234     .startns = startns,
235     .lplane = &kplane,
236     .dropped = &dropped,
237   };
238   struct marsh m2 = {
239     .slider = slider,
240     .nc = nc,
241     .next_frame = 1,
242     .frame_to_render = &last_frame,
243     .dm = notcurses_check_pixel_support(nc) ? 0 : 0.5 * delaymultiplier,
244     .ncv = ncv2,
245     .startns = startns,
246     .lplane = &kplane,
247     .dropped = &dropped,
248   };
249   int ret = -1;
250   if(pthread_create(&tid1, NULL, xray_thread, &m1)){
251     goto err;
252   }
253   if(pthread_create(&tid2, NULL, xray_thread, &m2)){
254     pthread_join(tid1, NULL);
255     goto err;
256   }
257   ret = pthread_join(tid1, NULL) | pthread_join(tid2, NULL);
258 
259 err:
260   ncvisual_destroy(ncv1);
261   ncvisual_destroy(ncv2);
262   ncplane_destroy(slider);
263   if(kplane){
264     ncplane_destroy(kplane);
265   }
266   return ret;
267 }
268